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