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}