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