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.Arrays;
020import java.util.Collections;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025import java.util.SortedSet;
026import java.util.stream.Collectors;
027
028import org.apache.commons.lang.StringUtils;
029
030import org.ametys.cms.repository.Content;
031import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO;
032import org.ametys.plugins.explorer.resources.ResourceCollection;
033import org.ametys.plugins.repository.AmetysObject;
034import org.ametys.plugins.repository.AmetysObjectIterable;
035import org.ametys.plugins.repository.AmetysObjectResolver;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.plugins.repository.CollectionIterable;
038import org.ametys.plugins.repository.UnknownAmetysObjectException;
039import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
040import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
041import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
042import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
043import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
044import org.ametys.plugins.repository.data.type.RepositoryModelItemType;
045import org.ametys.plugins.userdirectory.UserDirectoryPageHandler;
046import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
047import org.ametys.web.repository.page.Page;
048import org.ametys.web.repository.page.UnknownZoneException;
049import org.ametys.web.repository.page.Zone;
050import org.ametys.web.repository.site.Site;
051import org.ametys.web.repository.sitemap.Sitemap;
052import org.ametys.web.service.ServiceExtensionPoint;
053import org.ametys.web.skin.SkinsManager;
054
055
056/**
057 * Page representing a second-level page.
058 */
059public class TransitionalPage implements Page
060{
061    private Page _root;
062    private String _path;
063    private int _initialDepth;
064    private String _prefix;
065    private AmetysObjectResolver _resolver;
066    private UserDirectoryPageHandler _userDirectoryPageHandler;
067    private SynchronizableContentsCollectionDAO _syncContentsCollectionDAO;
068    private SkinsManager _skinsManager;
069
070    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _pageDataTypeExtensionPoint;
071    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneDataTypeExtensionPoint;
072    private ServiceExtensionPoint _serviceExtensionPoint;
073    private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneItemDataTypeExtensionPoint;
074    
075    /**
076     * Constructor.
077     * @param root the user directory root page.
078     * @param prefix the page's title.
079     * @param resolver the {@link AmetysObjectResolver}.
080     * @param path the path
081     * @param userDirectoryPageHandler the user directory page handler component
082     * @param syncContentsCollectionDAO The DAO for synchronizable collections
083     * @param skinsManager the skins manager
084     * @param pageDataTypeExtensionPoint the extension point with available data types for pages
085     * @param zoneDataTypeExtensionPoint the extension point with available data types for zones
086     * @param serviceExtensionPoint the service extension point
087     * @param zoneItemDataTypeExtensionPoint the extension point with available data types for zone items
088     */
089    public TransitionalPage(Page root, String prefix, String path, AmetysObjectResolver resolver, UserDirectoryPageHandler userDirectoryPageHandler, SynchronizableContentsCollectionDAO syncContentsCollectionDAO, SkinsManager skinsManager, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> pageDataTypeExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneDataTypeExtensionPoint, ServiceExtensionPoint serviceExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneItemDataTypeExtensionPoint)
090    {
091        _root = root;
092        _prefix = prefix;
093        _path = path;
094        _resolver = resolver;
095        _userDirectoryPageHandler = userDirectoryPageHandler;
096        _syncContentsCollectionDAO = syncContentsCollectionDAO;
097        _skinsManager = skinsManager;
098        
099        _initialDepth = _userDirectoryPageHandler.getDepth(_root);
100
101        _pageDataTypeExtensionPoint = pageDataTypeExtensionPoint;
102        _zoneDataTypeExtensionPoint = zoneDataTypeExtensionPoint;
103        _serviceExtensionPoint = serviceExtensionPoint;
104        _zoneItemDataTypeExtensionPoint = zoneItemDataTypeExtensionPoint;
105    }
106    
107    @Override
108    public int getDepth() throws AmetysRepositoryException
109    {
110        return _root.getDepth() + _path.split("/").length;
111    }
112
113    @Override
114    public Set<String> getReferers() throws AmetysRepositoryException
115    {
116        return null;
117    }
118
119    @Override
120    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
121    {
122        return null;
123    }
124
125    @Override
126    public String getTemplate() throws AmetysRepositoryException
127    {
128        return "page";
129    }
130
131    @Override
132    public String getTitle() throws AmetysRepositoryException
133    {
134        return StringUtils.upperCase(_prefix);
135    }
136    
137    @Override
138    public String getLongTitle() throws AmetysRepositoryException
139    {
140        return StringUtils.upperCase(_prefix);
141    }
142
143    @Override
144    public PageType getType() throws AmetysRepositoryException
145    {
146        return PageType.CONTAINER;
147    }
148
149    @Override
150    public String getURL() throws AmetysRepositoryException
151    {
152        throw new UnsupportedOperationException("getURL not supported on virtual user directory pages");
153    }
154
155    @Override
156    public LinkType getURLType() throws AmetysRepositoryException
157    {
158        throw new UnsupportedOperationException("getURLType not supported on virtual user directory pages");
159    }
160
161    @Override
162    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
163    {
164        if (!"default".equals(name))
165        {
166            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual transitional pages.");
167        }
168        
169        return new TransitionalZone(this, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
170    }
171
172    @Override
173    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
174    {
175        List<Zone> zones = new ArrayList<>();
176        zones.add(new TransitionalZone(this, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint));
177        return new CollectionIterable<>(zones);
178    }
179
180    @Override
181    public boolean hasZone(String name) throws AmetysRepositoryException
182    {
183        return "default".equals(name);
184    }
185
186    @Override
187    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
188    {
189        ArrayList<Page> children = new ArrayList<>();
190
191        int depth = _initialDepth - _path.split("/").length;
192        if (depth > 0)
193        {
194            SortedSet<String> transitionalPagesName = _userDirectoryPageHandler.getTransitionalPagesName(_root, _userDirectoryPageHandler.getName(_path));
195            for (String name : transitionalPagesName)
196            {
197                String pathName = _userDirectoryPageHandler.getPathName(name);
198                children.add(new TransitionalPage(_root, name, _path + "/" + pathName, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint));
199            }
200            
201            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, _userDirectoryPageHandler.getName(_path));
202            for (Entry<String, String> entry : userPagesContent.entrySet())
203            {
204                String contentTypeId = _userDirectoryPageHandler.getContentTypeId(_root);
205                String classificationMetadata = _userDirectoryPageHandler.getClassificationMetadata(_root);
206                Content content;
207                try
208                {
209                    content = _resolver.resolveById(entry.getValue());
210                }
211                catch (AmetysRepositoryException e)
212                {
213                    // content does not exist, skip to next iteration
214                    break;
215                }
216                if (content == null || !Arrays.asList(content.getTypes()).contains(contentTypeId) || !content.getMetadataHolder().hasMetadata(classificationMetadata))
217                {
218                    break;
219                }
220                
221                String metadataValue = _userDirectoryPageHandler.getTransformedClassificationMetadataValue(_root, content);
222                if (metadataValue != null && metadataValue.length() == _path.split("/").length)
223                {
224                    children.add(new UserPage(_root, content, _path, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint));
225                }
226            }
227        }
228        else
229        {
230            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, _path);
231            for (String contentId : userPagesContent.values())
232            {
233                try
234                {
235                    Content content = _resolver.resolveById(contentId);
236                    children.add(new UserPage(_root, content, _path, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint));
237                }
238                catch (UnknownAmetysObjectException e)
239                {
240                    System.out.println("Content does not exist anymore");
241                }
242            }
243        }
244        
245        return new CollectionIterable<>(children);
246    }
247
248    @Override
249    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException
250    {
251        if (includeInvisiblePage)
252        {
253            return getChildrenPages();
254        }
255        else
256        {
257            ArrayList<Page> children = new ArrayList<>();
258            return new CollectionIterable<>(children);
259        }
260    }
261    
262    @Override
263    public String getPathInSitemap() throws AmetysRepositoryException
264    {
265        String path = StringUtils.lowerCase(_path);
266        return _root.getPathInSitemap() + "/" + path;
267    }
268
269    @Override
270    public Site getSite() throws AmetysRepositoryException
271    {
272        return _root.getSite();
273    }
274
275    @Override
276    public String getSiteName() throws AmetysRepositoryException
277    {
278        return _root.getSiteName();
279    }
280
281    @Override
282    public Sitemap getSitemap() throws AmetysRepositoryException
283    {
284        return _root.getSitemap();
285    }
286
287    @Override
288    public String getSitemapName() throws AmetysRepositoryException
289    {
290        return _root.getSitemapName();
291    }
292
293    @SuppressWarnings("unchecked")
294    @Override
295    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
296    {
297        if (path.isEmpty())
298        {
299            throw new AmetysRepositoryException("path must be non empty");
300        }
301        
302        String completePath = _path + "/" + path;
303        int depth = _initialDepth - completePath.split("/").length + 1;
304        if (depth > 0)
305        {
306            String namePath = StringUtils.substringAfterLast(completePath, "/");
307            String parentPath = StringUtils.substringBeforeLast(completePath, "/");
308
309            SortedSet<String> transitionalPagesName = _userDirectoryPageHandler.getTransitionalPagesName(_root, parentPath);
310            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, parentPath);
311            String name = _userDirectoryPageHandler.getName(namePath);
312            if (transitionalPagesName.contains(name))
313            {
314                TransitionalPage page = new TransitionalPage(_root, name, completePath, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
315                return (A) page;
316            }
317            else if (userPagesContent.containsKey(name))
318            {
319                String contentId = userPagesContent.get(name);
320                Content syncContent = _resolver.resolveById(contentId);
321                UserPage page = new UserPage(_root, syncContent, parentPath, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
322                return (A) page;
323            }
324            else
325            {
326                throw new UnknownAmetysObjectException("No transitional page named " + name + " (full page path " + path + ").");
327            }
328        }
329        else
330        {
331            String userPath = StringUtils.substringBeforeLast(completePath, "/");
332            String contentName = StringUtils.substringAfterLast(completePath, "/");
333            
334            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, userPath);
335            if (userPagesContent.containsKey(contentName))
336            {
337                Content content = _resolver.resolveById(userPagesContent.get(contentName));
338                UserPage page = new UserPage(_root, content, userPath, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
339                return (A) page;
340            }
341            else
342            {
343                throw new UnknownAmetysObjectException("No user content named " + contentName + " (full page path " + path + ").");
344            }
345        }
346    }
347
348    @SuppressWarnings("unchecked")
349    @Override
350    public AmetysObjectIterable<? extends AmetysObject> getChildren() throws AmetysRepositoryException
351    {
352        return getChildrenPages();
353    }
354
355    @Override
356    public boolean hasChild(String name) throws AmetysRepositoryException
357    {
358        int depth = _initialDepth - _path.split("/").length;
359        if (depth > 0)
360        {
361            SortedSet<String> transitionalPagesName = _userDirectoryPageHandler.getTransitionalPagesName(_root, _path);
362            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, _path);
363            return transitionalPagesName.contains(name) || userPagesContent.containsKey(name);
364        }
365        else
366        {
367            Map<String, String> userPagesContent = _userDirectoryPageHandler.getUserPagesContent(_root, _path);
368            return userPagesContent.containsKey(name);
369        }
370    }
371    
372    @Override
373    public String getId() throws AmetysRepositoryException
374    {
375        // E.g: udtransitional://path?rootId=...
376        return "udtransitional://" + _path + "?rootId=" + _root.getId();
377    }
378
379    @Override
380    public String getName() throws AmetysRepositoryException
381    {
382        return StringUtils.lowerCase(_prefix);
383    }
384    
385    @SuppressWarnings("unchecked")
386    @Override
387    public Page getParent() throws AmetysRepositoryException
388    {
389        if (_path.split("/").length > 1)
390        {
391            String parentPath = StringUtils.substringBeforeLast(_path, "/");
392            String pathName = parentPath;
393            if (StringUtils.contains(pathName, "/"))
394            {
395                pathName = StringUtils.substringAfterLast(pathName, "/");
396            }
397            
398            String name = _userDirectoryPageHandler.getName(pathName);
399            return new TransitionalPage(_root, name, parentPath, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
400        }
401        else
402        {
403            return _root;
404        }
405    }
406
407    @Override
408    public String getParentPath() throws AmetysRepositoryException
409    {
410        if (_path.split("/").length > 1)
411        {
412            String path = StringUtils.lowerCase(_path);
413            return _root.getPath() + "/" + StringUtils.substringBeforeLast(path, "/");
414        }
415        else
416        {
417            return _root.getPath();
418        }
419    }
420
421    @Override
422    public String getPath() throws AmetysRepositoryException
423    {
424        return getParentPath() + "/" + getName();
425    }
426
427    public ModelLessDataHolder getDataHolder()
428    {
429        RepositoryData repositoryData = new MemoryRepositoryData(getName());
430        return new DefaultModelLessDataHolder(_pageDataTypeExtensionPoint, repositoryData);
431    }
432
433    @Override
434    public Set<String> getTags() throws AmetysRepositoryException
435    {
436        return Collections.emptySet();
437    }
438
439    @Override
440    public boolean isVisible() throws AmetysRepositoryException
441    {
442        return false;
443    }
444
445    @Override
446    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
447    {
448        return getChildrenPages().stream().collect(Collectors.toList()).get(index);
449    }
450    
451    public ModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
452    {
453        return null;
454    }
455}