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