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