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