001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.userdirectory.page;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.List;
021import java.util.Locale;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import javax.jcr.Node;
027import javax.jcr.RepositoryException;
028import javax.jcr.Value;
029
030import org.apache.commons.lang.StringUtils;
031
032import org.ametys.cms.repository.Content;
033import org.ametys.cms.repository.DefaultContent;
034import org.ametys.core.group.GroupIdentity;
035import org.ametys.core.right.RightManager;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection;
038import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO;
039import org.ametys.plugins.explorer.resources.ResourceCollection;
040import org.ametys.plugins.repository.ACLAmetysObject;
041import org.ametys.plugins.repository.AmetysObject;
042import org.ametys.plugins.repository.AmetysObjectIterable;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.AmetysRepositoryException;
045import org.ametys.plugins.repository.CollectionIterable;
046import org.ametys.plugins.repository.UnknownAmetysObjectException;
047import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
048import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
049import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
050import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
051import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
052import org.ametys.plugins.repository.data.type.RepositoryModelItemType;
053import org.ametys.plugins.repository.metadata.CompositeMetadata;
054import org.ametys.plugins.userdirectory.OrganisationChartPageHandler;
055import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
056import org.ametys.web.repository.page.Page;
057import org.ametys.web.repository.page.UnknownZoneException;
058import org.ametys.web.repository.page.Zone;
059import org.ametys.web.repository.site.Site;
060import org.ametys.web.repository.sitemap.Sitemap;
061import org.ametys.web.skin.Skin;
062import org.ametys.web.skin.SkinsManager;
063
064/**
065 * Page representing an orgUnit page.
066 */
067public class OrgUnitPage implements Page, ACLAmetysObject
068{
069    private static final String __ORGUNIT_PAGE_TEMPLATE = "udorgunit";
070    
071    private Page _root;
072    private String _title;
073    private AmetysObjectResolver _resolver;
074    private OrganisationChartPageHandler _organisationChartPageHandler;
075    private SynchronizableContentsCollectionDAO _syncContentsCollectionDAO;
076    private SkinsManager _skinsManager;
077    private String _path;
078    private Content _syncContent;
079    
080    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _pageDataTypeExtensionPoint;
081    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneDataTypeExtensionPoint;
082    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneItemDataTypeExtensionPoint;
083    
084    /**
085     * Constructor.
086     * @param root the root page.
087     * @param syncContent the synchronized content
088     * @param path the path
089     * @param resolver the {@link AmetysObjectResolver}.
090     * @param organisationChartPageHandler the organisation chart page handler
091     * @param syncContentsCollectionDAO The DAO for synchronizable collections
092     * @param skinsManager the skins manager
093     * @param pageDataTypeExtensionPoint the extension point with available data types for pages
094     * @param zoneDataTypeExtensionPoint the extension point with available data types for zones
095     * @param zoneItemDataTypeExtensionPoint the extension point with available data types for zone items
096     */
097    public OrgUnitPage(Page root, Content syncContent, String path, AmetysObjectResolver resolver, OrganisationChartPageHandler organisationChartPageHandler, SynchronizableContentsCollectionDAO syncContentsCollectionDAO, SkinsManager skinsManager, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> pageDataTypeExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneDataTypeExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneItemDataTypeExtensionPoint)
098    {
099        _root = root;
100        _path = path;
101        _resolver = resolver;
102        _organisationChartPageHandler = organisationChartPageHandler;
103        _syncContentsCollectionDAO = syncContentsCollectionDAO;
104        _skinsManager = skinsManager;
105        _syncContent = syncContent;
106        
107        _title = _syncContent.getTitle(new Locale(root.getSitemapName()));
108
109        _pageDataTypeExtensionPoint = pageDataTypeExtensionPoint;
110        _zoneDataTypeExtensionPoint = zoneDataTypeExtensionPoint;
111        _zoneItemDataTypeExtensionPoint = zoneItemDataTypeExtensionPoint;
112    }
113    
114    /**
115     * Returns the associated synchronizable {@link Content}.
116     * @return the associated synchronizable {@link Content}.
117     */
118    public Content getSyncContent()
119    {
120        return _syncContent;
121    }
122    
123    @Override
124    public int getDepth() throws AmetysRepositoryException
125    {
126        return _root.getDepth() + (_path != null ? _path.split("/").length : 0);
127    }
128
129    @Override
130    public Set<String> getReferers() throws AmetysRepositoryException
131    {
132        return null;
133    }
134
135    @Override
136    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
137    {
138        return null;
139    }
140
141    @Override
142    public String getTemplate() throws AmetysRepositoryException
143    {
144        Skin skin = _skinsManager.getSkin(getSite().getSkinId());
145        
146        if (skin.getTemplate(__ORGUNIT_PAGE_TEMPLATE) != null)
147        {
148            return __ORGUNIT_PAGE_TEMPLATE;
149        }
150        
151        return "page"; 
152    }
153
154    @Override
155    public String getTitle() throws AmetysRepositoryException
156    {
157        return _title;
158    }
159    
160    @Override
161    public String getLongTitle() throws AmetysRepositoryException
162    {
163        return _title;
164    }
165
166    @Override
167    public PageType getType() throws AmetysRepositoryException
168    {
169        return PageType.CONTAINER;
170    }
171
172    @Override
173    public String getURL() throws AmetysRepositoryException
174    {
175        throw new UnsupportedOperationException("#getURL is not supported on virtual orgUnit pages");
176    }
177
178    @Override
179    public LinkType getURLType() throws AmetysRepositoryException
180    {
181        throw new UnsupportedOperationException("#getURLType is not supported on virtual orgUnit pages");
182    }
183
184    @Override
185    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
186    {
187        if (!"default".equals(name))
188        {
189            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual orgUnit pages.");
190        }
191        
192        return new OrgUnitZone(this, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint);
193    }
194
195    @Override
196    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
197    {
198        ArrayList<Zone> zones = new ArrayList<>();
199        zones.add(new OrgUnitZone(this, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint));
200        return new CollectionIterable<>(zones);
201    }
202
203    @Override
204    public boolean hasZone(String name) throws AmetysRepositoryException
205    {
206        return "default".equals(name);
207    }
208
209    @Override
210    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
211    {
212        ArrayList<Page> children = new ArrayList<>();
213        
214        for (Content content : _organisationChartPageHandler.getChildContents(_syncContent))
215        {
216            children.add(new OrgUnitPage(_root, content, _path + "/" + content.getName() , _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint));
217        }
218        
219        return new CollectionIterable<>(children);
220    }
221
222    @Override
223    public String getPathInSitemap() throws AmetysRepositoryException
224    {
225        return _root.getPathInSitemap() + "/" + _path;
226    }
227
228    @Override
229    public Site getSite() throws AmetysRepositoryException
230    {
231        return _root.getSite();
232    }
233
234    @Override
235    public String getSiteName() throws AmetysRepositoryException
236    {
237        return _root.getSiteName();
238    }
239
240    @Override
241    public Sitemap getSitemap() throws AmetysRepositoryException
242    {
243        return _root.getSitemap();
244    }
245
246    @Override
247    public String getSitemapName() throws AmetysRepositoryException
248    {
249        return _root.getSitemapName();
250    }
251
252    @SuppressWarnings("unchecked")
253    @Override
254    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
255    {
256        if (path.isEmpty())
257        {
258            throw new AmetysRepositoryException("path must be non empty");
259        }
260        
261        Content childContent = _organisationChartPageHandler.getChildFromPath(_syncContent, path);
262        if (childContent != null)
263        {
264            OrgUnitPage page = new OrgUnitPage(_root, childContent, _path + "/" + path , _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint);
265            return (A) page;
266        }
267        
268        return null;
269    }
270    
271
272    @SuppressWarnings("unchecked")
273    @Override
274    public AmetysObjectIterable<? extends AmetysObject> getChildren() throws AmetysRepositoryException
275    {
276        return getChildrenPages();
277    }
278
279    @Override
280    public boolean hasChild(String name) throws AmetysRepositoryException
281    {
282        return !_organisationChartPageHandler.getChildContents(_syncContent).stream().filter(c -> c.getName().equals(name)).collect(Collectors.toList()).isEmpty();
283    }
284    
285    @Override
286    public String getId() throws AmetysRepositoryException
287    {
288        // E.g: udorgunit://path?rootId=...&contentId=...
289        return "udorgunit://" + _path + "?rootId=" + _root.getId() + "&contentId=" + _syncContent.getId();
290    }
291
292    @Override
293    public String getName() throws AmetysRepositoryException
294    {
295        return _syncContent.getName();
296    }
297    
298    @SuppressWarnings("unchecked")
299    @Override
300    public Page getParent() throws AmetysRepositoryException
301    {
302        Content parentContent = _organisationChartPageHandler.getParentContent(_syncContent);
303        if (parentContent != null)
304        {
305            return new OrgUnitPage(_root, parentContent, StringUtils.substringBeforeLast(_path, "/"), _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint);
306        }
307
308        return _root;
309    }
310
311    @Override
312    public String getParentPath() throws AmetysRepositoryException
313    {
314        if (_path.contains("/"))
315        {
316            return _root.getPath() + "/" + StringUtils.substringBeforeLast(_path, "/");
317        }
318        else
319        {
320            return _root.getPath();
321        }
322    }
323
324    @Override
325    public String getPath() throws AmetysRepositoryException
326    {
327        return _root.getPath() + "/" + _path;
328    }
329
330    public ModelLessDataHolder getDataHolder()
331    {
332        RepositoryData repositoryData = new MemoryRepositoryData(getName());
333        return new DefaultModelLessDataHolder(_pageDataTypeExtensionPoint, repositoryData);
334    }
335
336    @Override
337    public Set<String> getTags() throws AmetysRepositoryException
338    {
339        return Collections.emptySet();
340    }
341
342    @Override
343    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException
344    {
345        return getChildrenPages();
346    }
347
348    @Override
349    public boolean isVisible() throws AmetysRepositoryException
350    {
351        return true;
352    }
353
354    @Override
355    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
356    {
357        throw new UnknownAmetysObjectException("There is no child for orgUnit page");
358    }
359
360    @Override
361    public Set<String> getAllowedProfilesForAnyConnectedUser()
362    {
363        return Collections.EMPTY_SET;
364    }
365
366    @Override
367    public Set<String> getDeniedProfilesForAnyConnectedUser()
368    {
369        return Collections.EMPTY_SET;
370    }
371    
372    @Override
373    public Set<String> getAllowedProfilesForAnonymous()
374    {
375        return Collections.EMPTY_SET;
376    }
377
378    @Override
379    public Set<String> getDeniedProfilesForAnonymous()
380    {
381        List<String> collectionIds = _getCollectionIds();
382        for (String collectionId : collectionIds)
383        {
384            SynchronizableContentsCollection collection = _syncContentsCollectionDAO.getSynchronizableContentsCollection(collectionId);
385            String restrictedField = collection.getRestrictedField();
386            if (!StringUtils.isEmpty(restrictedField) && _isContentRestricted(restrictedField))
387            {
388                return Collections.singleton(RightManager.READER_PROFILE_ID);
389            }
390        }
391        return Collections.EMPTY_SET;
392    }
393
394    @Override
395    public Set<String> getAllowedProfilesForUser(UserIdentity user)
396    {
397        return Collections.EMPTY_SET;
398    }
399
400    @Override
401    public Map<UserIdentity, Set<String>> getAllowedProfilesForUsers()
402    {
403        return Collections.EMPTY_MAP;
404    }
405
406    @Override
407    public Set<UserIdentity> getAllowedUsers(String profileId)
408    {
409        return Collections.EMPTY_SET;
410    }
411
412    @Override
413    public Map<GroupIdentity, Set<String>> getAllowedProfilesForGroups()
414    {
415        return Collections.EMPTY_MAP;
416    }
417
418    @Override
419    public Set<GroupIdentity> getAllowedGroups(String profileId)
420    {
421        return Collections.EMPTY_SET;
422    }
423    
424    @Override
425    public Set<String> getDeniedProfilesForUser(UserIdentity user)
426    {
427        return Collections.EMPTY_SET;
428    }
429
430    @Override
431    public Map<UserIdentity, Set<String>> getDeniedProfilesForUsers()
432    {
433        return Collections.EMPTY_MAP;
434    }
435
436    @Override
437    public Set<UserIdentity> getDeniedUsers(String profileId)
438    {
439        return Collections.EMPTY_SET;
440    }
441
442    @Override
443    public Map<GroupIdentity, Set<String>> getDeniedProfilesForGroups()
444    {
445        return Collections.EMPTY_MAP;
446    }
447
448    @Override
449    public Set<GroupIdentity> getDeniedGroups(String profileId)
450    {
451        return Collections.EMPTY_SET;
452    }
453    
454    @Override
455    public boolean isInheritanceDisallowed()
456    {
457        return false;
458    }
459    
460    private boolean _isContentRestricted (String metadataPath)
461    {
462        CompositeMetadata metadataHolder = _syncContent.getMetadataHolder();
463        
464        String[] pathSegments = metadataPath.split("/");
465        
466        for (int i = 0;  i < pathSegments.length - 1; i++)
467        {
468            if (!metadataHolder.hasMetadata(pathSegments[i]))
469            {
470                return false;
471            }
472            metadataHolder = metadataHolder.getCompositeMetadata(pathSegments[i]);
473        }
474        
475        String metadataName = pathSegments[pathSegments.length - 1];
476        return metadataHolder.getBoolean(metadataName, false);
477    }
478    
479    private List<String> _getCollectionIds() throws AmetysRepositoryException
480    {
481        List<String> collectionIds = new ArrayList<>();
482        
483        if (_syncContent instanceof DefaultContent)
484        {
485            try
486            {
487                Node node = ((DefaultContent) _syncContent).getNode();
488                if (node.hasProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY))
489                {
490                    Value[] values = node.getProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY).getValues();
491                    for (Value value : values)
492                    {
493                        collectionIds.add(value.getString());
494                    }
495                }
496            }
497            catch (RepositoryException e)
498            {
499                throw new AmetysRepositoryException("Failed to get linked synchronizable collections for content " + _syncContent.getId(), e);
500            }
501        }
502        
503        return collectionIds;
504    }
505    
506    public ModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
507    {
508        return null;
509    }
510}