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