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.repository; 017 018import java.io.IOException; 019import java.net.MalformedURLException; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.Set; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.components.source.SourceUtil; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.generation.ServiceableGenerator; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.SaxBuffer; 034import org.apache.cocoon.xml.XMLUtils; 035import org.apache.commons.lang.StringUtils; 036import org.apache.commons.lang.exception.ExceptionUtils; 037import org.apache.excalibur.source.Source; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040import org.xml.sax.ContentHandler; 041import org.xml.sax.SAXException; 042 043import org.ametys.cms.content.GetContentAction; 044import org.ametys.cms.contenttype.ContentTypeDescriptor; 045import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 046import org.ametys.cms.contenttype.ContentTypesHelper; 047import org.ametys.cms.contenttype.DynamicContentTypeDescriptorExtentionPoint; 048import org.ametys.cms.repository.Content; 049import org.ametys.cms.tag.Tag; 050import org.ametys.cms.tag.TagProviderExtensionPoint; 051import org.ametys.core.DevMode; 052import org.ametys.core.DevMode.DEVMODE; 053import org.ametys.core.right.RightManager; 054import org.ametys.core.right.RightManager.RightResult; 055import org.ametys.core.ui.ClientSideElement.ScriptFile; 056import org.ametys.core.util.IgnoreRootHandler; 057import org.ametys.core.util.URIUtils; 058import org.ametys.plugins.core.ui.ObfuscatedException; 059import org.ametys.plugins.repository.AmetysObject; 060import org.ametys.plugins.repository.AmetysObjectIterable; 061import org.ametys.plugins.repository.AmetysRepositoryException; 062import org.ametys.plugins.repository.provider.WorkspaceSelector; 063import org.ametys.runtime.authentication.AccessDeniedException; 064import org.ametys.runtime.authentication.AuthorizationRequiredException; 065import org.ametys.runtime.exception.ServiceUnavailableException; 066import org.ametys.web.WebConstants; 067import org.ametys.web.cache.monitoring.Constants; 068import org.ametys.web.cache.monitoring.process.access.ResourceAccessComponent; 069import org.ametys.web.cache.monitoring.process.access.impl.PageElementResourceAccess; 070import org.ametys.web.cache.monitoring.process.access.impl.PageElementResourceAccess.PageElementType; 071import org.ametys.web.cache.monitoring.process.access.impl.PageResourceAccess; 072import org.ametys.web.cache.pageelement.PageElementCache; 073import org.ametys.web.content.ContentHelper; 074import org.ametys.web.renderingcontext.RenderingContext; 075import org.ametys.web.renderingcontext.RenderingContextHandler; 076import org.ametys.web.repository.content.SharedContent; 077import org.ametys.web.repository.page.ContentTypesAssignmentHandler; 078import org.ametys.web.repository.page.ModifiablePage; 079import org.ametys.web.repository.page.MoveablePage; 080import org.ametys.web.repository.page.Page; 081import org.ametys.web.repository.page.Page.PageType; 082import org.ametys.web.repository.page.ServicesAssignmentHandler; 083import org.ametys.web.repository.page.SitemapElement; 084import org.ametys.web.repository.page.Zone; 085import org.ametys.web.repository.page.ZoneItem; 086import org.ametys.web.repository.page.ZoneItem.ZoneType; 087import org.ametys.web.repository.site.Site; 088import org.ametys.web.service.Service; 089import org.ametys.web.service.ServiceExtensionPoint; 090import org.ametys.web.skin.Skin; 091import org.ametys.web.skin.SkinTemplate; 092import org.ametys.web.skin.SkinTemplateZone; 093import org.ametys.web.skin.SkinsManager; 094 095/** 096 * Generator for SAXing <code>Content</code> associated with a Page.<br> 097 * SAX events are like :<br> 098 * <pageContents><br> 099 * <zone id="..."><br> 100 * <i><!-- XHTML content --></i><br> 101 * </zone><br> 102 * ...<br> 103 * </pageContents/><br> 104 */ 105public class PageGenerator extends ServiceableGenerator 106{ 107 private ServiceExtensionPoint _serviceExtPt; 108 private ContentTypeExtensionPoint _contentTypeExtPt; 109 private SkinsManager _skinsManager; 110 private TagProviderExtensionPoint _tagProviderEP; 111 private PageElementCache _zoneItemCache; 112 private WorkspaceSelector _workspaceSelector; 113 private RenderingContextHandler _renderingContextHandler; 114 private ContentTypesHelper _contentTypeHelper; 115 private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP; 116 117 /** The content type assignment handler. */ 118 private ContentTypesAssignmentHandler _cTypeAssignmentHandler; 119 120 /** The service assignment handler. */ 121 private ServicesAssignmentHandler _serviceAssignmentHandler; 122 123 /** The resource access monitoring component */ 124 private ResourceAccessComponent _resourceAccessMonitor; 125 126 /** The monitored resource access */ 127 private PageResourceAccess _pageAccess; 128 129 private ContentHelper _contentHelper; 130 131 /** The right manager */ 132 private RightManager _rightManager; 133 134 private int _zoneItemsInCache; 135 private int _zoneItemsSaxed; 136 private int _zonesSaxed; 137 138 private Logger _timeLogger = LoggerFactory.getLogger("org.ametys.web.rendering.time"); 139 140 @Override 141 public void service(ServiceManager serviceManager) throws ServiceException 142 { 143 super.service(serviceManager); 144 _serviceExtPt = (ServiceExtensionPoint) serviceManager.lookup(ServiceExtensionPoint.ROLE); 145 _contentTypeExtPt = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 146 _skinsManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE); 147 _tagProviderEP = (TagProviderExtensionPoint) serviceManager.lookup(TagProviderExtensionPoint.ROLE); 148 _zoneItemCache = (PageElementCache) serviceManager.lookup(PageElementCache.ROLE + "/zoneItem"); 149 _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE); 150 _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 151 _cTypeAssignmentHandler = (ContentTypesAssignmentHandler) serviceManager.lookup(ContentTypesAssignmentHandler.ROLE); 152 _serviceAssignmentHandler = (ServicesAssignmentHandler) serviceManager.lookup(ServicesAssignmentHandler.ROLE); 153 _resourceAccessMonitor = (ResourceAccessComponent) serviceManager.lookup(ResourceAccessComponent.ROLE); 154 _contentTypeHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 155 _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) serviceManager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); 156 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 157 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 158 } 159 160 @Override 161 public void generate() throws IOException, SAXException, ProcessingException 162 { 163 long t0 = System.currentTimeMillis(); 164 165 _zoneItemsInCache = 0; 166 _zoneItemsSaxed = 0; 167 _zonesSaxed = 0; 168 169 Request request = ObjectModelHelper.getRequest(objectModel); 170 171 Page page = (Page) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE); 172 String title = page.getTitle(); 173 174 RenderingContext renderingContext = _renderingContextHandler.getRenderingContext(); 175 String workspace = _workspaceSelector.getWorkspace(); 176 String siteName = page.getSiteName(); 177 178 if (page.getType() != PageType.CONTAINER) 179 { 180 throw new IllegalStateException("Cannot invoke the PageGenerator on a Page without a PageContent"); 181 } 182 183 // Monitor the access to this page. 184 _pageAccess = (PageResourceAccess) request.getAttribute(Constants.REQUEST_ATTRIBUTE_PAGEACCESS); 185 186 if (_pageAccess != null) 187 { 188 _pageAccess.setRenderingContext(renderingContext); 189 _pageAccess.setWorkspaceJCR(workspace); 190 _resourceAccessMonitor.addAccessRecord(_pageAccess); 191 } 192 193 contentHandler.startDocument(); 194 AttributesImpl attrs = new AttributesImpl(); 195 attrs.addCDATAAttribute("title", title); 196 attrs.addCDATAAttribute("long-title", page.getLongTitle()); 197 attrs.addCDATAAttribute("id", page.getId()); 198 XMLUtils.startElement(contentHandler, "page", attrs); 199 200 try 201 { 202 XMLUtils.startElement(contentHandler, "metadata"); 203 page.dataToSAX(contentHandler); 204 XMLUtils.endElement(contentHandler, "metadata"); 205 206 // Tags 207 XMLUtils.startElement(contentHandler, "tags"); 208 Set<String> tags = page.getTags(); 209 for (String tagName : tags) 210 { 211 Map<String, Object> contextParameters = new HashMap<>(); 212 contextParameters.put("siteName", siteName); 213 214 Tag tag = _tagProviderEP.getTag(tagName, contextParameters); 215 216 if (tag != null) 217 { 218 // tag may be null if it has been registered on the page and then removed from the application 219 AttributesImpl tagattrs = new AttributesImpl(); 220 if (tag.getParentName() != null) 221 { 222 tagattrs.addCDATAAttribute("parent", tag.getParentName()); 223 } 224 225 XMLUtils.startElement(contentHandler, tagName, tagattrs); 226 tag.getTitle().toSAX(contentHandler); 227 XMLUtils.endElement(contentHandler, tagName); 228 } 229 } 230 XMLUtils.endElement(contentHandler, "tags"); 231 } 232 catch (AmetysRepositoryException e) 233 { 234 _pageAccess = null; 235 throw new ProcessingException("Unable to SAX page metadata", e); 236 } 237 238 long t1 = System.currentTimeMillis(); 239 240 AttributesImpl pcattrs = new AttributesImpl(); 241 pcattrs.addCDATAAttribute("modifiable", Boolean.toString(page instanceof ModifiablePage)); 242 pcattrs.addCDATAAttribute("moveable", Boolean.toString(page instanceof MoveablePage)); 243 XMLUtils.startElement(contentHandler, "pageContents", pcattrs); 244 245 try 246 { 247 // Iterate on existing zones 248 for (Zone zone : page.getZones()) 249 { 250 String zoneName = zone.getName(); 251 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 252 253 _saxZone(page, zoneName, zoneItems, workspace, siteName, renderingContext); 254 } 255 256 // Iterate on defined zone (that are not existing) 257 SkinTemplate skinTemplate = _getTemplateDefinition(page); 258 if (skinTemplate != null) 259 { 260 for (SkinTemplateZone zoneDef : skinTemplate.getZones().values()) 261 { 262 if (!page.hasZone(zoneDef.getId())) 263 { 264 _saxZone(page, zoneDef.getId(), null, workspace, siteName, renderingContext); 265 } 266 } 267 } 268 } 269 catch (AmetysRepositoryException ex) 270 { 271 _pageAccess = null; 272 throw new ProcessingException("Unable to get Content", ex); 273 } 274 275 long t2 = System.currentTimeMillis(); 276 _timeLogger.debug("Zones processing time: {} ms", t2 - t1); 277 if (getLogger().isInfoEnabled()) 278 { 279 getLogger().info("PageGenerator\t/" + page.getSiteName() + "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "\t" + page.getId() + "\tprocessing time (in ms):\t" + (t2 - t0) + "\tRendering context:\t" + renderingContext + "\tSaxing (zones, total zoneItems, zoneItems from cache):\t" + _zonesSaxed + "\t" + _zoneItemsSaxed + "\t" + _zoneItemsInCache); 280 } 281 282 XMLUtils.endElement(contentHandler, "pageContents"); 283 XMLUtils.endElement(contentHandler, "page"); 284 contentHandler.endDocument(); 285 286 _timeLogger.debug("Page processing time: {} ms", t2 - t0); 287 288 _pageAccess = null; 289 } 290 291 /** 292 * Sax a zone 293 * @param page The page 294 * @param zoneName The zone in the page to sax 295 * @param zoneItems The items of the zone or null 296 * @param workspace the workspace 297 * @param site the site's name 298 * @param renderingContext the rendering context 299 * @throws SAXException if an error occurs while saxing 300 * @throws IOException if an I/O exception occurs 301 * @throws ProcessingException if an error occurs 302 */ 303 private void _saxZone(Page page, String zoneName, AmetysObjectIterable<? extends ZoneItem> zoneItems, String workspace, String site, RenderingContext renderingContext) throws SAXException, IOException, ProcessingException 304 { 305 _zonesSaxed++; 306 307 AmetysObjectIterable<? extends ZoneItem> localZoneItems = zoneItems; 308 309 AttributesImpl zoneAttrs = new AttributesImpl(); 310 zoneAttrs.addCDATAAttribute("name", zoneName); 311 312 if (localZoneItems == null || !localZoneItems.iterator().hasNext()) 313 { 314 // zone is empty => try to inherit 315 Zone parentPageZone = _inherit(page, page, zoneName); 316 if (parentPageZone != null) 317 { 318 zoneAttrs.addCDATAAttribute("inherited", parentPageZone.getSitemapElement().getId()); 319 320 localZoneItems = parentPageZone.getZoneItems(); 321 } 322 } 323 324 Request request = ObjectModelHelper.getRequest(objectModel); 325 request.setAttribute(Zone.class.getName(), zoneName); 326 327 XMLUtils.startElement(contentHandler, "zone", zoneAttrs); 328 329 if (renderingContext == RenderingContext.BACK) 330 { 331 _saxAvailableContentTypes(page, zoneName); 332 _saxAvailableServices(page, zoneName); 333 } 334 335 _saxZoneItems(page, localZoneItems, workspace, site, renderingContext); 336 337 XMLUtils.endElement(contentHandler, "zone"); 338 request.setAttribute(Zone.class.getName(), null); 339 340 } 341 342 /** 343 * Generate the list of available services for the given zone. 344 * @param page the page. 345 * @param zoneName the zone name in the page. 346 * @throws SAXException if something goes wrong when saxing the available services 347 */ 348 private void _saxAvailableServices(Page page, String zoneName) throws SAXException 349 { 350 Set<String> services = _serviceAssignmentHandler.getAvailableServices(page, zoneName); 351 352 XMLUtils.startElement(contentHandler, "available-services"); 353 354 for (String service : services) 355 { 356 AttributesImpl attrs = new AttributesImpl(); 357 attrs.addCDATAAttribute("id", service); 358 XMLUtils.createElement(contentHandler, "service", attrs); 359 } 360 361 XMLUtils.endElement(contentHandler, "available-services"); 362 } 363 364 /** 365 * Generate the list of available content types for the given zone. 366 * @param page the page. 367 * @param zoneName the zone name in the page. 368 * @throws SAXException if something goes wrong when saxing the available content types 369 */ 370 private void _saxAvailableContentTypes(Page page, String zoneName) throws SAXException 371 { 372 Set<String> cTypes = _cTypeAssignmentHandler.getAvailableContentTypes(page, zoneName, true); 373 374 XMLUtils.startElement(contentHandler, "available-content-types"); 375 376 for (String cType : cTypes) 377 { 378 AttributesImpl attrs = new AttributesImpl(); 379 attrs.addCDATAAttribute("id", cType); 380 XMLUtils.createElement(contentHandler, "content-type", attrs); 381 } 382 383 XMLUtils.endElement(contentHandler, "available-content-types"); 384 } 385 386 /** 387 * Sax zone items 388 * @param page the page 389 * @param zoneItems The zone items to sax 390 * @param workspace the workspace 391 * @param site the site's name 392 * @param renderingContext the rendering context 393 * @throws SAXException if an error occurs while saxing 394 * @throws IOException if an I/O exception occurs 395 * @throws ProcessingException if an error occurs 396 */ 397 private void _saxZoneItems(Page page, AmetysObjectIterable< ? extends ZoneItem> zoneItems, String workspace, String site, RenderingContext renderingContext) throws SAXException, IOException, ProcessingException 398 { 399 if (zoneItems == null) 400 { 401 return; 402 } 403 404 Request request = ObjectModelHelper.getRequest(objectModel); 405 406 for (ZoneItem zoneItem : zoneItems) 407 { 408 _saxZoneItem(page, workspace, site, renderingContext, request, zoneItem); 409 } 410 } 411 412 private void _handleZoneAccess(PageElementResourceAccess zoneAccess, boolean cacheable, boolean hit) 413 { 414 if (zoneAccess != null) 415 { 416 zoneAccess.setCacheable(cacheable); 417 zoneAccess.setCacheHit(hit); 418 } 419 } 420 421 private void _saxZoneItem(Page page, String workspace, String site, RenderingContext renderingContext, Request request, ZoneItem zoneItem) throws SAXException, ProcessingException, IOException 422 { 423 long t0 = System.currentTimeMillis(); 424 425 _zoneItemsSaxed++; 426 427 String id = zoneItem.getId(); 428 ZoneType type = zoneItem.getType(); 429 430 request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM, zoneItem); 431 432 AttributesImpl zoneItemAttrs = new AttributesImpl(); 433 zoneItemAttrs.addCDATAAttribute("id", id); 434 435 PageElementResourceAccess zoneAccess = _pageAccess != null ? _pageAccess.createPageElementAccess(id, PageElementType.fromZoneItemType(type)) : null; 436 437 // try to get content from cache only if it is cacheable 438 SaxBuffer cachedContent = null; 439 Exception ex = null; 440 441 boolean isCacheable = false; 442 if (type == ZoneType.CONTENT) 443 { 444 // a Content is always cacheable 445 isCacheable = true; 446 } 447 else if (type == ZoneType.SERVICE) 448 { 449 String serviceId = zoneItem.getServiceId(); 450 Service service = _serviceExtPt.getExtension(serviceId); 451 452 if (service != null) 453 { 454 try 455 { 456 isCacheable = service.isCacheable(page, zoneItem); 457 } 458 catch (Exception e) 459 { 460 ex = new ProcessingException("Error testing the service cachability.", e); 461 } 462 } 463 // if service is null, an exception will be thrown later 464 } 465 466 _handleZoneAccess(zoneAccess, isCacheable, false); 467 468 request.setAttribute("IsZoneItemCacheable", isCacheable); 469 470 if (isCacheable) 471 { 472 cachedContent = _zoneItemCache.getPageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext); 473 } 474 475 if (cachedContent != null) 476 { 477 _handleZoneAccess(zoneAccess, true, true); 478 479 _zoneItemsInCache++; 480 481 request.setAttribute("IsZoneItemCacheable", true); 482 483 cachedContent.toSAX(contentHandler); 484 } 485 else 486 { 487 488 SaxBuffer buffer = null; 489 ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer 490 if (isCacheable) 491 { 492 buffer = new SaxBuffer(); 493 handler = buffer; 494 } 495 else 496 { 497 handler = contentHandler; 498 } 499 500 XMLUtils.startElement(handler, "zoneItem", zoneItemAttrs); 501 502 XMLUtils.startElement(handler, "information"); 503 XMLUtils.createElement(handler, "type", type.toString()); 504 505 Object result = null; 506 if (type == ZoneType.CONTENT) 507 { 508 if (getLogger().isDebugEnabled()) 509 { 510 Content content = zoneItem.getContent(); 511 getLogger().debug("Processing content " + content.getId() + " / " + content.getPath()); 512 } 513 514 result = _saxContentZoneItem(zoneItem, handler, request); 515 } 516 else if (type == ZoneType.SERVICE) 517 { 518 if (getLogger().isDebugEnabled()) 519 { 520 getLogger().debug("Processing service " + zoneItem.getServiceId()); 521 } 522 523 result = _saxServiceZoneItem(zoneItem, handler, ex); 524 } 525 526 Source src = null; 527 if (result instanceof Source) 528 { 529 src = (Source) result; 530 } 531 else 532 { 533 ex = (Exception) result; 534 } 535 536 XMLUtils.endElement(handler, "information"); 537 538 _saxSource(handler, src, ex, isCacheable); 539 540 XMLUtils.endElement(handler, "zoneItem"); 541 542 // finally store the buffered data in the cache and SAX it to the pipeline 543 if (buffer != null) 544 { 545 buffer.toSAX(contentHandler); 546 _zoneItemCache.storePageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext, buffer); 547 } 548 } 549 550 // Monitor the access to this zone item. 551 _resourceAccessMonitor.addAccessRecord(zoneAccess); 552 553 // Empty content request attributes 554 request.setAttribute(Content.class.getName(), null); 555 // Empty zone item request attribute 556 request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM, null); 557 // Empty zone item cacheable attribute 558 request.setAttribute("IsZoneItemCacheable", null); 559 560 _timeLogger.debug("Zone item {} processing time: {} ms", id, System.currentTimeMillis() - t0); 561 } 562 563 private Object _saxContentZoneItem(ZoneItem zoneItem, ContentHandler handler, Request request) throws SAXException, MalformedURLException, IOException 564 { 565 try 566 { 567 Content content = zoneItem.getContent(); 568 String viewName = StringUtils.defaultString(zoneItem.getViewName(), "main"); 569 570 XMLUtils.createElement(handler, "contentId", content.getId()); 571 XMLUtils.createElement(handler, "contentName", content.getName()); 572 XMLUtils.createElement(handler, "metadataSetName", viewName); 573 if (content instanceof SharedContent) 574 { 575 XMLUtils.createElement(handler, "sharedContent", "true"); 576 } 577 578 String contentTypeId = _contentTypeHelper.getContentTypeIdForRendering(content); 579 580 ContentTypeDescriptor contentType = _contentTypeExtPt.getExtension(contentTypeId); 581 if (contentType == null) 582 { 583 contentType = _dynamicCTDescriptorEP.getExtension(contentTypeId); 584 } 585 586 if (contentType == null) 587 { 588 return new IllegalStateException("The content type '" + contentTypeId + "' is referenced but does not exist"); 589 } 590 else 591 { 592 AttributesImpl contentTypeAttrs = new AttributesImpl(); 593 contentTypeAttrs.addCDATAAttribute("id", contentType.getId()); 594 XMLUtils.startElement(handler, "type-information", contentTypeAttrs); 595 596 contentType.getLabel().toSAX(handler, "label"); 597 contentType.getDescription().toSAX(handler, "description"); 598 599 if (contentType.getIconGlyph() != null) 600 { 601 XMLUtils.createElement(handler, "iconGlyph", contentType.getIconGlyph()); 602 } 603 if (contentType.getIconDecorator() != null) 604 { 605 XMLUtils.createElement(handler, "iconDecorator", contentType.getIconDecorator()); 606 } 607 608 if (contentType.getSmallIcon() != null) 609 { 610 XMLUtils.createElement(handler, "smallIcon", contentType.getSmallIcon()); 611 XMLUtils.createElement(handler, "mediumIcon", contentType.getMediumIcon()); 612 XMLUtils.createElement(handler, "largeIcon", contentType.getLargeIcon()); 613 } 614 615 XMLUtils.startElement(handler, "css"); 616 for (ScriptFile cssFile : contentType.getCSSFiles()) 617 { 618 _saxCSSFile(handler, cssFile); 619 } 620 XMLUtils.endElement(handler, "css"); 621 622 XMLUtils.endElement(handler, "type-information"); 623 624 String url = "cocoon://_plugins/" + contentType.getPluginName() + "/" + contentType.getId() + ".html"; 625 Map<String, String> urlParams = _contentHelper.getContentViewUrlParameters(content, viewName, "html"); 626 627 // FIXME use a context 628 request.setAttribute(Content.class.getName(), content); 629 request.setAttribute(GetContentAction.RESULT_CONTENTTYPE, contentTypeId); 630 return resolver.resolveURI(URIUtils.buildURI(url, urlParams)); 631 } 632 } 633 catch (AmetysRepositoryException e) 634 { 635 return new ProcessingException("Unable to get content property", e); 636 } 637 } 638 639 private Object _saxServiceZoneItem(ZoneItem zoneItem, ContentHandler handler, Exception ex) throws SAXException, MalformedURLException, IOException 640 { 641 String serviceId = zoneItem.getServiceId(); 642 Service service = _serviceExtPt.getExtension(serviceId); 643 644 if (service == null) 645 { 646 return new ProcessingException("Unable to get service for name '" + serviceId + "'"); 647 } 648 else if (ex == null) // If an exception was caught while testing the service cacheability, do not generate 649 { 650 AttributesImpl serviceAttrs = new AttributesImpl(); 651 serviceAttrs.addCDATAAttribute("id", service.getId()); 652 XMLUtils.startElement(handler, "type-information", serviceAttrs); 653 654 service.getLabel().toSAX(handler, "label"); 655 service.getDescription().toSAX(handler, "description"); 656 657 if (service.getIconGlyph() != null) 658 { 659 XMLUtils.createElement(handler, "iconGlyph", service.getIconGlyph()); 660 } 661 if (service.getIconDecorator() != null) 662 { 663 XMLUtils.createElement(handler, "iconDecorator", service.getIconDecorator()); 664 } 665 if (service.getSmallIcon() != null) 666 { 667 XMLUtils.createElement(handler, "smallIcon", service.getSmallIcon()); 668 XMLUtils.createElement(handler, "mediumIcon", service.getMediumIcon()); 669 } 670 671 XMLUtils.startElement(handler, "css"); 672 for (ScriptFile cssFile : service.getCSSFiles()) 673 { 674 _saxCSSFile(handler, cssFile); 675 } 676 XMLUtils.endElement(handler, "css"); 677 678 XMLUtils.endElement(handler, "type-information"); 679 680 return resolver.resolveURI(service.getURL(), null, PageGeneratorHelper.getParameters(service, zoneItem)); 681 } 682 else 683 { 684 return ex; 685 } 686 } 687 688 private void _saxCSSFile(ContentHandler handler, ScriptFile cssFile) throws SAXException 689 { 690 AttributesImpl fileAttrs = new AttributesImpl(); 691 if (!cssFile.isLangSpecific()) 692 { 693 String rtlMode = cssFile.getRtlMode(); 694 if (rtlMode != null && !"all".equals(rtlMode)) 695 { 696 fileAttrs.addCDATAAttribute("rtl", rtlMode); 697 } 698 699 XMLUtils.createElement(handler, "file", fileAttrs, cssFile.getPath()); 700 } 701 else 702 { 703 fileAttrs.addCDATAAttribute("lang", "true"); 704 XMLUtils.startElement(handler, "file", fileAttrs); 705 706 String defaultLang = cssFile.getDefaultLang(); 707 Map<String, String> langPaths = cssFile.getLangPaths(); 708 709 for (Entry<String, String> langPath : langPaths.entrySet()) 710 { 711 AttributesImpl langAttrs = new AttributesImpl(); 712 713 String codeLang = langPath.getKey(); 714 langAttrs.addCDATAAttribute("code", codeLang); 715 if (codeLang.equals(defaultLang)) 716 { 717 langAttrs.addCDATAAttribute("default", "true"); 718 } 719 720 XMLUtils.createElement(handler, "lang", langAttrs, langPath.getValue()); 721 } 722 723 XMLUtils.endElement(handler, "file"); 724 } 725 } 726 727 private void _saxSource(ContentHandler handler, Source src, Exception ex, boolean isCacheable) throws SAXException, IOException, ProcessingException 728 { 729 if (src == null) 730 { 731 Throwable e = _obfuscate(ex, isCacheable); 732 if (e instanceof ObfuscatedException obfuscatedException) 733 { 734 obfuscatedException.reveal(); 735 getLogger().error("Unable to display zone item", ex); 736 obfuscatedException.obfuscate(); 737 } 738 else 739 { 740 getLogger().error("Unable to display zone item", ex); 741 } 742 743 _saxError(handler, e); 744 } 745 else 746 { 747 try 748 { 749 SourceUtil.toSAX(src, new IgnoreRootHandler(handler)); 750 } 751 catch (ProcessingException e) 752 { 753 if (_throwException(e)) 754 { 755 getLogger().error("Unable to display zone item", e); 756 throw e; 757 } 758 else 759 { 760 Throwable cause = _obfuscate(e.getCause(), isCacheable); 761 if (cause instanceof ObfuscatedException obfuscatedException) 762 { 763 obfuscatedException.reveal(); 764 getLogger().error("Unable to display zone item", cause); 765 obfuscatedException.obfuscate(); 766 } 767 else 768 { 769 getLogger().error("Unable to display zone item", cause); 770 } 771 _saxError(handler, cause); 772 } 773 } 774 finally 775 { 776 resolver.release(src); 777 } 778 } 779 } 780 781 private String _getType(ZoneItem zoneItem) 782 { 783 ZoneType type = zoneItem.getType(); 784 785 if (type == ZoneType.CONTENT) 786 { 787 return "CONTENT"; 788 } 789 else 790 { 791 return "SERVICE:" + zoneItem.getServiceId(); 792 } 793 } 794 795 /** 796 * Get the template definition for a page 797 * @param sitemapElement The page. Cannot be null. 798 * @return The template definition. Null if the page is not a container or if the template is not declared. 799 */ 800 private SkinTemplate _getTemplateDefinition(SitemapElement sitemapElement) 801 { 802 String templateName = sitemapElement.getTemplate(); 803 if (templateName == null) 804 { 805 return null; 806 } 807 808 Site site = sitemapElement.getSite(); 809 String skinId = site.getSkinId(); 810 try 811 { 812 Skin skinDef = _skinsManager.getSkin(skinId); 813 return skinDef.getTemplate(templateName); 814 } 815 catch (IllegalStateException e) 816 { 817 getLogger().error("Cannot get template definition for page '" + sitemapElement.getId() + "' using template '" + templateName + "' in skin '" + skinId + "'"); 818 return null; 819 } 820 } 821 822 /** 823 * Try to inherit the zone (as it is empty) 824 * @param childPage The child page that do inherit. Cannot be null 825 * @param page The page to inherit. Cannot be null 826 * @param zoneName The zone name in the page to inherit. Cannot be null or empty 827 * @return The zone inherited or null. 828 */ 829 private Zone _inherit(Page childPage, SitemapElement page, String zoneName) 830 { 831 // The page has an existing zone at this place ? 832 if (page.hasZone(zoneName)) 833 { 834 Zone zone = page.getZone(zoneName); 835 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 836 837 // With data ? 838 if (zoneItems.iterator().hasNext()) 839 { 840 // This is it (end of the recursion) 841 return zone; 842 } 843 } 844 845 // Get the definition for the zone 846 SkinTemplateZone zoneDef; 847 848 Site site = page.getSite(); 849 String skinId = site.getSkinId(); 850 String templateName = page.getTemplate(); 851 try 852 { 853 SkinTemplate templateDef = _getTemplateDefinition(page); 854 zoneDef = templateDef.getZone(zoneName); 855 } 856 catch (IllegalStateException e) 857 { 858 getLogger().error("The page '" + childPage.getId() + "' cannot inherit a zone '" + zoneName + "' of template '" + templateName + "' in skin '" + skinId + "' as asked for page '" + page.getId() + "' in site '" + site.getName() + "'", e); 859 return null; 860 } 861 862 // This zone is not defined for the template 863 if (zoneDef == null) 864 { 865 getLogger().warn("The page '" + childPage.getId() + "' cannot inherit the undefined zone '" + zoneName + "' of template '" + templateName + "' in skin '" + skinId + "' as asked for page '" + page.getId() + "' in site '" + site.getName() + "'."); 866 return null; 867 } 868 869 // Support inheritance ? 870 if (!zoneDef.hasInheritance()) 871 { 872 return null; 873 } 874 875 // Get the parent page (that is a container page) 876 SitemapElement parentPage = page; 877 do 878 { 879 AmetysObject parentObject = parentPage.getParent(); 880 if (parentObject instanceof SitemapElement pc) 881 { 882 parentPage = pc; 883 } 884 else 885 { 886 // inheritance goes back to the root 887 return null; 888 } 889 } 890 while (parentPage.getTemplate() == null); 891 892 // Get the name of the zone which will be the inheritance source 893 String parentPageTemplate = parentPage.getTemplate(); 894 String inheritanceSrc = zoneDef.getInheritance(parentPageTemplate); 895 if (inheritanceSrc == null) 896 { 897 // No inheritance for this template 898 return null; 899 } 900 901 // Finally we will inherit from the parentPage and the zone inheritanceSrc 902 return _inherit(childPage, parentPage, inheritanceSrc); 903 } 904 905 /** 906 * Test if the error has to be thrown instead of SAXing it as an error zone item. 907 * @param ex the exception. 908 * @return true to throw the exception, false to catch it and SAX it as an error zone item. 909 */ 910 private boolean _throwException(Exception ex) 911 { 912 return _renderingContextHandler.getRenderingContext() == RenderingContext.FRONT 913 && ( 914 _isExceptionType(ex, AuthorizationRequiredException.class) 915 || _isExceptionType(ex, AccessDeniedException.class) 916 // See CMS-12302 if you want to change that one day 917 || _isExceptionType(ex, ServiceUnavailableException.class) 918 ); 919 } 920 921 private boolean _isExceptionType(Throwable throwable, Class clazz) 922 { 923 return ExceptionUtils.indexOfThrowable(throwable, clazz) > -1; 924 } 925 926 private void _saxError(ContentHandler handler, Throwable e) throws SAXException 927 { 928 XMLUtils.startElement(handler, "zone-item-error"); 929 930 XMLUtils.createElement(handler, "exception-message", e != null ? StringUtils.defaultString(e.getMessage()) : ""); 931 XMLUtils.createElement(handler, "exception-stack-trace", StringUtils.defaultString(ExceptionUtils.getFullStackTrace(e))); 932 933 XMLUtils.endElement(handler, "zone-item-error"); 934 } 935 936 private Throwable _obfuscate(Throwable throwable, boolean isCacheable) 937 { 938 if (!DEVMODE.PRODUCTION.equals(DevMode.getDeveloperMode()) // Do not read dev mode from request to prevent override from request param 939 || !isCacheable && _rightManager.currentUserHasRight("Runtime_Rights_Admin_Access", "/admin").equals(RightResult.RIGHT_ALLOW)) 940 { 941 return throwable; 942 } 943 else 944 { 945 return ObfuscatedException.obfuscate(throwable); 946 } 947 } 948}