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