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