001/*
002 *  Copyright 2010 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.web.inputdata;
017
018import org.apache.avalon.framework.configuration.Configurable;
019import org.apache.avalon.framework.configuration.Configuration;
020import org.apache.avalon.framework.configuration.ConfigurationException;
021import org.apache.avalon.framework.context.Context;
022import org.apache.avalon.framework.context.ContextException;
023import org.apache.avalon.framework.context.Contextualizable;
024import org.apache.avalon.framework.logger.AbstractLogEnabled;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.cocoon.ProcessingException;
029import org.apache.cocoon.components.ContextHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.lang3.LocaleUtils;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.SAXException;
037
038import org.ametys.core.right.RightManager;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.plugins.repository.AmetysObjectIterable;
042import org.ametys.plugins.repository.AmetysRepositoryException;
043import org.ametys.plugins.repository.metadata.CompositeMetadata;
044import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType;
045import org.ametys.plugins.repository.metadata.MultilingualStringHelper;
046import org.ametys.web.pageaccess.RestrictedPagePolicy;
047import org.ametys.web.renderingcontext.RenderingContext;
048import org.ametys.web.renderingcontext.RenderingContextHandler;
049import org.ametys.web.repository.page.Page;
050import org.ametys.web.repository.page.Page.PageType;
051import org.ametys.web.repository.page.PagesContainer;
052import org.ametys.web.repository.site.Site;
053import org.ametys.web.repository.site.SiteManager;
054import org.ametys.web.repository.sitemap.Sitemap;
055
056/**
057 * {@link InputData} for SAXing events about the current sitemap.
058 */
059public class SitemapInputData extends AbstractLogEnabled implements InputData, Contextualizable, Configurable, Serviceable
060{
061    /** Prefix for sitemap namespace. */
062    public static final String NAMESPACE_PREFIX = "sitemap";
063    /** URI for sitemap namespace. */
064    public static final String NAMESPACE_URI = "http://www.ametys.org/inputdata/sitemap/3.0";
065    
066    // Constants for current path management
067    private static final int PATH_DESCENDANT = -2;
068    private static final int PATH_NOT_IN_PATH = -1;
069    private static final int PATH_IN_PATH = 0;
070    private static final int PATH_CURRENT = 1;
071    
072    /** Avalon context. */
073    protected Context _context;
074    /** Configured initial depth. */
075    protected int _initialDepth;
076    /** Configured descendant depth. */
077    protected int _descendantDepth;
078    /** CMS Sites manager */ 
079    protected SiteManager _siteManager;
080    /** The right manager */
081    protected RightManager _rightManager;
082    /** The rendering context handler. */
083    protected RenderingContextHandler _renderingContextHandler;
084    /** The current user provider */
085    protected CurrentUserProvider _currentUserProvider;
086    
087    @Override
088    public void service(ServiceManager manager) throws ServiceException
089    {
090        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
091        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
092        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
093        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
094    }
095    
096    @Override
097    public void contextualize(Context context) throws ContextException
098    {
099        _context = context;
100    }
101
102    @Override
103    public void configure(Configuration configuration) throws ConfigurationException
104    {
105        _initialDepth = configuration.getChild("initial-depth", true).getValueAsInteger(2);
106        _descendantDepth = configuration.getChild("descendant-depth", true).getValueAsInteger(1);
107    }
108    
109    @Override
110    public boolean isCacheable(Site site, Page page)
111    {
112        return site.getRestrictedPagePolicy() == RestrictedPagePolicy.DISPLAYED;
113    }
114    
115    @Override
116    public void toSAX(ContentHandler handler) throws SAXException, ProcessingException
117    {
118        Request request = ContextHelper.getRequest(_context);
119        
120        String siteName = (String) request.getAttribute("site");
121        Site site = _siteManager.getSite(siteName);
122        
123        RestrictedPagePolicy policy = site.getRestrictedPagePolicy();
124
125        String sitemapLanguage = (String) request.getAttribute("sitemapLanguage");
126        Sitemap sitemap = site.getSitemap(sitemapLanguage);
127        
128        Page page = (Page) request.getAttribute(Page.class.getName());
129        
130        try
131        {
132            handler.startPrefixMapping(NAMESPACE_PREFIX, NAMESPACE_URI);
133
134            AttributesImpl attrs = new AttributesImpl();
135            attrs.addCDATAAttribute(NAMESPACE_URI, "site", NAMESPACE_PREFIX + ":site", site.getName());
136            attrs.addCDATAAttribute(NAMESPACE_URI, "lang", NAMESPACE_PREFIX + ":lang", sitemap.getName());
137            attrs.addCDATAAttribute(NAMESPACE_URI, "id", NAMESPACE_PREFIX + ":id", sitemap.getId());
138
139            XMLUtils.startElement(handler, "sitemap", attrs);
140
141            // Process recursively pages
142            _saxPages(handler, sitemap, page != null ? page.getPathInSitemap() : null, 1, -1, policy, _currentUserProvider.getUser());
143
144            XMLUtils.endElement(handler, "sitemap");
145            handler.endPrefixMapping(NAMESPACE_PREFIX);
146        }
147        catch (AmetysRepositoryException e)
148        {
149            throw new ProcessingException("Unable to process sitemap", e);
150        }
151    }
152
153    /**
154     * SAX pages.
155     * @param handler the content handler to SAX into.
156     * @param composite the pages container.
157     * @param currentPagePath the path to the current page.
158     * @param currentDepth the current depth.
159     * @param descendantDepth the descendant depth.
160     * @param policy the site's {@link RestrictedPagePolicy}.
161     * @param userIdentity the current front-office's user.
162     * @throws SAXException if an error occurs while SAXing.
163     * @throws AmetysRepositoryException if an error occurs.
164     */
165    protected void _saxPages(ContentHandler handler, PagesContainer composite, String currentPagePath, int currentDepth, int descendantDepth, RestrictedPagePolicy policy, UserIdentity userIdentity) throws SAXException, AmetysRepositoryException
166    {
167        AmetysObjectIterable<? extends Page> pages = composite.getChildrenPages();
168        
169        RenderingContext renderingContext = _renderingContextHandler.getRenderingContext();
170        boolean inBackOffice = renderingContext == RenderingContext.BACK || renderingContext == RenderingContext.PREVIEW;
171        
172        for (Page page : pages)
173        {
174            if (inBackOffice || policy == RestrictedPagePolicy.DISPLAYED || _rightManager.hasReadAccess(userIdentity, page) && _processPage(page))
175            {
176                if (page.isVisible() || (StringUtils.isNotBlank(currentPagePath) && currentPagePath.startsWith(page.getPathInSitemap())))
177                {
178                    // Sax only visible pages except if the page is in current path
179                    _saxPage(handler, currentPagePath, currentDepth, descendantDepth, policy, userIdentity, page);
180                }
181            }
182        }
183    }
184
185    /**
186     * Sax a page
187     * @param handler the content handler to sax into
188     * @param currentPagePath The path of current page
189     * @param currentDepth The depth of current page
190     * @param descendantDepth The descendant depth
191     * @param policy the restricted page policy
192     * @param userIdentity the user identity
193     * @param page the page
194     * @throws SAXException if an error occurs while saxing
195     */
196    protected void _saxPage(ContentHandler handler, String currentPagePath, int currentDepth, int descendantDepth, RestrictedPagePolicy policy, UserIdentity userIdentity, Page page) throws SAXException
197    {
198        String path = page.getPathInSitemap();
199        PageType type = page.getType();
200        AttributesImpl attrs = new AttributesImpl();
201
202        attrs.addCDATAAttribute(NAMESPACE_URI, "id", NAMESPACE_PREFIX + ":id", page.getId());
203        attrs.addCDATAAttribute(NAMESPACE_URI, "name", NAMESPACE_PREFIX + ":name", page.getName());
204        attrs.addCDATAAttribute(NAMESPACE_URI, "title", NAMESPACE_PREFIX + ":title", page.getTitle());
205        attrs.addCDATAAttribute(NAMESPACE_URI, "long-title", NAMESPACE_PREFIX + ":long-title", page.getLongTitle());
206        attrs.addCDATAAttribute(NAMESPACE_URI, "path", NAMESPACE_PREFIX + ":path", path);
207        attrs.addCDATAAttribute(NAMESPACE_URI, "type", NAMESPACE_PREFIX + ":type", page.getType().name());
208        
209        if (!page.isVisible())
210        {
211            attrs.addCDATAAttribute(NAMESPACE_URI, "invisible", NAMESPACE_PREFIX + ":invisible", "true");
212        }
213        
214        if (!_rightManager.hasAnonymousReadAccess(page))
215        {
216            attrs.addCDATAAttribute(NAMESPACE_URI, "restricted", NAMESPACE_PREFIX + ":restricted", "true");
217        }
218        
219        // Add a flag if there is data
220        if (type == PageType.CONTAINER)
221        {
222            attrs.addCDATAAttribute(NAMESPACE_URI, "container", NAMESPACE_PREFIX + ":container", "true");
223        }
224        // Add URL if found
225        else if (type == PageType.LINK)
226        {
227            attrs.addAttribute(NAMESPACE_URI, "link", NAMESPACE_PREFIX + ":link", "CDATA", page.getURL());
228            attrs.addAttribute(NAMESPACE_URI, "link-type", NAMESPACE_PREFIX + ":link-type", "CDATA", page.getURLType().name());
229        }
230
231        // Current status (in-path, current)
232        int inPath = _saxCurrentStatus(currentPagePath, path, attrs);
233
234        // Metadatas
235        _saxMetadatas(page, attrs);
236        
237        // Tags
238        // FIXME use a nested element
239        _saxTags(page, attrs);
240
241        XMLUtils.startElement(handler, "page", attrs);
242
243        // Continue if current depth is less than initial depth
244        // or if we are inside current path or in current page descendants
245        if (currentDepth <= _initialDepth || inPath >= PATH_IN_PATH || (inPath == PATH_DESCENDANT && descendantDepth <= _descendantDepth))
246        {
247            int descendant = (inPath == PATH_CURRENT) ? 0 : (inPath == PATH_DESCENDANT ? descendantDepth + 1 : -1);
248            _saxPages(handler, page, currentPagePath, currentDepth + 1, descendant, policy, userIdentity);
249        }
250
251        XMLUtils.endElement(handler, "page");
252    }
253
254    /**
255     * Determine if the page will be processed.<br>
256     * Default implementation returns true.
257     * @param page the page.
258     * @return <code>true</code> if the page will be proceed, <code>false</code> otherwise.
259     * @throws AmetysRepositoryException if an error occurs.
260     */
261    protected boolean _processPage(Page page) throws AmetysRepositoryException
262    {
263        return true;
264    }
265
266    /**
267     * SAX metadatas.
268     * @param page the page.
269     * @param attrs the attributes to populate.
270     * @throws AmetysRepositoryException if an error occurs.
271     */
272    protected void _saxMetadatas(Page page, AttributesImpl attrs) throws AmetysRepositoryException
273    {
274        CompositeMetadata metadataHolder = page.getMetadataHolder();
275
276        for (String metadataName : metadataHolder.getMetadataNames())
277        {
278            MetadataType type = metadataHolder.getType(metadataName);
279            
280            // SAX only non composite, non binary, non rich text and non multivalued metadatas
281            if (!metadataHolder.isMultiple(metadataName))
282            {
283                switch (type)
284                {
285                    case STRING:
286                    case DOUBLE:
287                    case DATE:
288                    case LONG:
289                    case BOOLEAN:
290                        attrs.addCDATAAttribute(metadataName, metadataHolder.getString(metadataName));
291                        break;
292                    case MULTILINGUAL_STRING:
293                        attrs.addCDATAAttribute(metadataName, MultilingualStringHelper.getValue(metadataHolder, metadataName, LocaleUtils.toLocale(page.getSitemapName())));
294                        break;
295                    default:
296                        break;
297                }
298            }
299            
300            if (type != MetadataType.COMPOSITE && type != MetadataType.USER && type != MetadataType.BINARY
301                    && type != MetadataType.RICHTEXT && !metadataHolder.isMultiple(metadataName))
302            {
303                attrs.addCDATAAttribute(metadataName, metadataHolder.getString(metadataName));
304            }
305        }
306    }
307
308    /**
309     * SAX metadatas.
310     * @param page the page.
311     * @param attrs the attributes to populate.
312     * @throws AmetysRepositoryException if an error occurs.
313     */
314    protected void _saxTags(Page page, AttributesImpl attrs) throws AmetysRepositoryException
315    {
316        for (String tag : page.getTags())
317        {
318            // FIXME use nested element not this dirty XML
319            attrs.addCDATAAttribute("PLUGIN_TAGS_" + tag, "empty");
320        }
321    }
322
323    /**
324     * SAX current status.
325     * @param currentPagePath the path to the current page.
326     * @param pagePath the path to the page to process.
327     * @param attrs the attributes to populate.
328     * @return the current status.
329     */
330    protected int _saxCurrentStatus(String currentPagePath, String pagePath, AttributesImpl attrs)
331    {
332        int result = PATH_NOT_IN_PATH;
333
334        if (currentPagePath == null)
335        {
336            return PATH_NOT_IN_PATH;
337        }
338
339        // Si la page fait partie du path
340        boolean isPageCurrent = currentPagePath.equals(pagePath);
341
342        if (currentPagePath.startsWith(pagePath + "/") || isPageCurrent)
343        {
344            attrs.addCDATAAttribute(NAMESPACE_URI, "in-path", NAMESPACE_PREFIX + ":in-path", "true");
345            result = PATH_IN_PATH;
346        }
347        else if (pagePath.startsWith(currentPagePath))
348        {
349            result = PATH_DESCENDANT;
350        }
351
352        // Si la page est celle demandée
353        if (isPageCurrent)
354        {
355            attrs.addCDATAAttribute(NAMESPACE_URI, "current", NAMESPACE_PREFIX + ":current", "true");
356            result = PATH_CURRENT;
357        }
358
359        return result;
360    }
361}