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