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