001/*
002 *  Copyright 2011 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.blog.repository;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.NoSuchElementException;
023import java.util.Set;
024
025import javax.jcr.RepositoryException;
026
027import org.ametys.cms.tag.Tag;
028import org.ametys.cms.tag.TagHelper;
029import org.ametys.cms.tag.TagProviderExtensionPoint;
030import org.ametys.cms.tag.jcr.JCRTag;
031import org.ametys.core.util.I18nUtils;
032import org.ametys.plugins.blog.BlogCacheManager;
033import org.ametys.plugins.explorer.resources.ResourceCollection;
034import org.ametys.plugins.repository.AmetysObject;
035import org.ametys.plugins.repository.AmetysObjectIterable;
036import org.ametys.plugins.repository.AmetysObjectIterator;
037import org.ametys.plugins.repository.AmetysObjectResolver;
038import org.ametys.plugins.repository.AmetysRepositoryException;
039import org.ametys.plugins.repository.CollectionIterable;
040import org.ametys.plugins.repository.EmptyIterable;
041import org.ametys.plugins.repository.TraversableAmetysObject;
042import org.ametys.plugins.repository.UnknownAmetysObjectException;
043import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
044import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
045import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
046import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
047import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
048import org.ametys.web.data.type.ModelItemTypeExtensionPoint;
049import org.ametys.web.repository.page.Page;
050import org.ametys.web.repository.page.SitemapElement;
051import org.ametys.web.repository.page.UnknownZoneException;
052import org.ametys.web.repository.page.Zone;
053import org.ametys.web.repository.site.Site;
054import org.ametys.web.repository.sitemap.Sitemap;
055import org.ametys.web.repository.tag.WEBJCRTagProvider;
056import org.ametys.web.service.ServiceExtensionPoint;
057import org.ametys.web.skin.SkinsManager;
058
059/**
060 * Virtual page representing the Post list page.
061 */
062public class VirtualTagsPage implements Page
063{
064    
065    /** The page name. */
066    public static final String NAME = "tags";
067    
068    private AmetysObjectResolver _resolver;
069    private BlogCacheManager _cacheManager;
070    private I18nUtils _i18nUtils;
071    private TagProviderExtensionPoint _tagProviderEP;
072    private SitemapElement _root;
073    private SkinsManager _skinsManager;
074    private String _title;
075
076    private ModelItemTypeExtensionPoint _pageDataTypeExtensionPoint;
077    private ModelItemTypeExtensionPoint _zoneDataTypeExtensionPoint;
078    private ServiceExtensionPoint _serviceExtensionPoint;
079    private ModelItemTypeExtensionPoint _zoneItemDataTypeExtensionPoint;
080    
081    /**
082     * Constructor.
083     * @param resolver the {@link AmetysObjectResolver}.
084     * @param cacheManager the {@link AmetysObjectResolver}.
085     * @param skinsManager the {@link SkinsManager}.
086     * @param i18nCache the i18n utils
087     * @param tagProviderEP the tags provider
088     * @param root the blog root page.
089     * @param title the page's title.
090     * @param pageDataTypeExtensionPoint the extension point with available data types for pages
091     * @param zoneDataTypeExtensionPoint the extension point with available data types for zones
092     * @param serviceExtensionPoint The service extension point
093     * @param zoneItemDataTypeExtensionPoint the extension point with available data types for zone items
094     */
095    public VirtualTagsPage(AmetysObjectResolver resolver, BlogCacheManager cacheManager, SkinsManager skinsManager, I18nUtils i18nCache, TagProviderExtensionPoint tagProviderEP, String title, SitemapElement root, ModelItemTypeExtensionPoint pageDataTypeExtensionPoint, ModelItemTypeExtensionPoint zoneDataTypeExtensionPoint, ServiceExtensionPoint serviceExtensionPoint, ModelItemTypeExtensionPoint zoneItemDataTypeExtensionPoint)
096    {
097        _resolver = resolver;
098        _cacheManager = cacheManager;
099        _skinsManager = skinsManager;
100        _i18nUtils = i18nCache;
101        _tagProviderEP = tagProviderEP;
102        _root = root;
103        _title = title;
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        int depth = 0;
115        if (_root instanceof Page)
116        {
117            depth = ((Page) _root).getDepth();
118        }
119        
120        return depth + 1;
121    }
122
123    @Override
124    public Set<String> getReferers() throws AmetysRepositoryException
125    {
126        return null;
127    }
128
129    @Override
130    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
131    {
132        return null;
133    }
134
135    @Override
136    public String getTemplate() throws AmetysRepositoryException
137    {
138        return null;
139    }
140
141    @Override
142    public String getTitle() throws AmetysRepositoryException
143    {
144        return _title;
145    }
146    
147    @Override
148    public String getLongTitle() throws AmetysRepositoryException
149    {
150        return _title;
151    }
152
153    @Override
154    public PageType getType() throws AmetysRepositoryException
155    {
156        return PageType.NODE;
157    }
158
159    @Override
160    public String getURL() throws AmetysRepositoryException
161    {
162        throw new UnsupportedOperationException("getURL not supported on virtual blog pages");
163    }
164
165    @Override
166    public LinkType getURLType() throws AmetysRepositoryException
167    {
168        throw new UnsupportedOperationException("getURLType not supported on virtual blog pages");
169    }
170
171    @Override
172    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
173    {
174        throw new UnknownZoneException("There is no zone on the blog tags page.");
175    }
176
177    @Override
178    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
179    {
180        return new EmptyIterable<>();
181    }
182
183    @Override
184    public boolean hasZone(String name) throws AmetysRepositoryException
185    {
186        return false;
187    }
188
189    @Override
190    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
191    {
192        ArrayList<Page> children = new ArrayList<>();
193     
194        Set<String> tagNames = _cacheManager.getTags(getSiteName(), getSitemapName());
195        
196        WEBJCRTagProvider provider = (WEBJCRTagProvider) _tagProviderEP.getExtension(WEBJCRTagProvider.class.getName());
197        try
198        {
199            Map<String, Object> contextualParams = new HashMap<>();
200            contextualParams.put("siteName", getSiteName());
201            
202            TraversableAmetysObject rootNode = provider.getRootNode(contextualParams);
203            AmetysObjectIterable<AmetysObject> it = rootNode.getChildren();
204            for (AmetysObject object : it)
205            {
206                if (object instanceof JCRTag)
207                {
208                    String tagName = object.getName();
209                    
210                    Tag tag = _tagProviderEP.getTag(object.getName(), contextualParams);
211                    
212                    Set<String> descendantAndItSelfNames = TagHelper.getDescendantNames(tag, true);
213                    
214                    if (!Collections.disjoint(tagNames, descendantAndItSelfNames))
215                    {
216                        String title = _i18nUtils.translate(tag.getTitle(), getSitemapName());
217                        
218                        VirtualTagPage page = new VirtualTagPage(_resolver, _cacheManager, _skinsManager, _tagProviderEP, _i18nUtils, tagName, title, _root, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
219                        
220                        children.add(page);
221                    }
222                }
223            }
224        }
225        catch (RepositoryException e)
226        {
227            throw new AmetysRepositoryException("Unable to get tags pages", e);
228        }
229        
230        return new CollectionIterable<>(children);
231    }
232    
233    @Override
234    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePages) throws AmetysRepositoryException
235    {
236        return getChildrenPages();
237    }
238    
239    @Override
240    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
241    {
242        if (index < 0)
243        {
244            throw new AmetysRepositoryException("Child page index cannot be negative");
245        }
246        
247        AmetysObjectIterable< ? extends Page> childPages = getChildrenPages();
248        AmetysObjectIterator< ? extends Page> it = childPages.iterator();
249        
250        try
251        {
252            it.skip(index);
253            return it.next();
254        }
255        catch (NoSuchElementException e)
256        {
257            throw new UnknownAmetysObjectException("There's no child page at index " + index + " for page " + this.getId());
258        }
259    }
260    
261    @Override
262    public String getPathInSitemap() throws AmetysRepositoryException
263    {
264        StringBuilder path = new StringBuilder(_root.getPathInSitemap());
265        if (path.length() > 0)
266        {
267            path.append('/');
268        }
269        path.append(NAME);
270        
271        return path.toString();
272    }
273
274    @Override
275    public Site getSite() throws AmetysRepositoryException
276    {
277        return _root.getSite();
278    }
279
280    @Override
281    public String getSiteName() throws AmetysRepositoryException
282    {
283        return _root.getSiteName();
284    }
285
286    @Override
287    public Sitemap getSitemap() throws AmetysRepositoryException
288    {
289        return _root.getSitemap();
290    }
291
292    @Override
293    public String getSitemapName() throws AmetysRepositoryException
294    {
295        return _root.getSitemapName();
296    }
297
298    @SuppressWarnings("unchecked")
299    @Override
300    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
301    {
302        if (path.isEmpty())
303        {
304            throw new AmetysRepositoryException("Path must be non empty");
305        }
306        
307        int slashPos = path.indexOf('/');
308        
309        String childName = slashPos == -1 ? path : path.substring(0, slashPos);
310        
311        Set<String> tagNames = _cacheManager.getTags(getSiteName(), getSitemapName());
312        
313        Map<String, Object> contextualParams = new HashMap<>();
314        contextualParams.put("siteName", getSiteName());
315        
316        Tag tag = _tagProviderEP.getTag(childName, contextualParams);
317        if (tag == null)
318        {
319            throw new UnknownAmetysObjectException("No child named " + childName + " exists in parent " + getId());
320        }
321        
322        Set<String> descendantAndItSelfNames = TagHelper.getDescendantNames(tag, true);
323        if (Collections.disjoint(tagNames, descendantAndItSelfNames))
324        {
325            throw new UnknownAmetysObjectException("No child named " + childName + " exists in parent " + getId());
326        }
327        
328        String title = _i18nUtils.translate(tag.getTitle(), getSitemapName());
329        
330        VirtualTagPage page = new VirtualTagPage(_resolver, _cacheManager, _skinsManager, _tagProviderEP, _i18nUtils, childName, title, _root, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _serviceExtensionPoint, _zoneItemDataTypeExtensionPoint);
331        
332        if (slashPos == -1)
333        {
334            return (A) page;
335        }
336        else
337        {
338            return (A) page.getChild(path.substring(slashPos + 1));
339        }
340    }
341
342    @SuppressWarnings("unchecked")
343    @Override
344    public AmetysObjectIterable<? extends AmetysObject> getChildren() throws AmetysRepositoryException
345    {
346        return getChildrenPages();
347    }
348
349    @Override
350    public boolean hasChild(String name) throws AmetysRepositoryException
351    {
352        return _cacheManager.hasTag(getSiteName(), getSitemapName(), name);
353    }
354    
355    @Override
356    public String getId() throws AmetysRepositoryException
357    {
358        return "blog-category://" + NAME + "?rootId=" + _root.getId();
359    }
360
361    @Override
362    public String getName() throws AmetysRepositoryException
363    {
364        return NAME;
365    }
366
367    @SuppressWarnings("unchecked")
368    @Override
369    public SitemapElement getParent() throws AmetysRepositoryException
370    {
371        return _root;
372    }
373
374    @Override
375    public String getParentPath() throws AmetysRepositoryException
376    {
377        return _root.getPath();
378    }
379
380    @Override
381    public String getPath() throws AmetysRepositoryException
382    {
383        StringBuilder buff = new StringBuilder(getParentPath());
384        if (!getParentPath().isEmpty())
385        {
386            buff.append('/');
387        }
388        buff.append(NAME);
389        
390        return buff.toString();
391    }
392
393    public ModelLessDataHolder getDataHolder()
394    {
395        RepositoryData repositoryData = new MemoryRepositoryData(getName());
396        return new DefaultModelLessDataHolder(_pageDataTypeExtensionPoint, repositoryData);
397    }
398
399    @Override
400    public Set<String> getTags() throws AmetysRepositoryException
401    {
402        return Collections.emptySet();
403    }
404
405    @Override
406    public boolean isVisible() throws AmetysRepositoryException
407    {
408        return true;
409    }
410    
411    public ModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
412    {
413        return null;
414    }
415}