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}