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 _handleZoneAccess(PageElementResourceAccess zoneAccess, boolean cacheable, boolean hit) 401 { 402 if (zoneAccess != null) 403 { 404 zoneAccess.setCacheable(cacheable); 405 zoneAccess.setCacheHit(hit); 406 } 407 } 408 409 private void _saxZoneItem(Page page, String workspace, String site, RenderingContext renderingContext, Request request, ZoneItem zoneItem) throws SAXException, ProcessingException, IOException 410 { 411 long t0 = System.currentTimeMillis(); 412 413 _zoneItemsSaxed++; 414 415 String id = zoneItem.getId(); 416 ZoneType type = zoneItem.getType(); 417 418 request.setAttribute(ZoneItem.class.getName(), zoneItem); 419 420 AttributesImpl zoneItemAttrs = new AttributesImpl(); 421 zoneItemAttrs.addCDATAAttribute("id", id); 422 423 PageElementResourceAccess zoneAccess = _pageAccess != null ? _pageAccess.createPageElementAccess(id, PageElementType.fromZoneItemType(type)) : null; 424 425 // try to get content from cache 426 SaxBuffer cachedContent = _zoneItemCache.getPageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext); 427 428 if (cachedContent != null) 429 { 430 _handleZoneAccess(zoneAccess, true, true); 431 432 _zoneItemsInCache++; 433 434 request.setAttribute("IsZoneItemCacheable", true); 435 436 cachedContent.toSAX(contentHandler); 437 } 438 else 439 { 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 SaxBuffer buffer = null; 472 ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer 473 if (isCacheable) 474 { 475 buffer = new SaxBuffer(); 476 handler = buffer; 477 } 478 else 479 { 480 handler = contentHandler; 481 } 482 483 XMLUtils.startElement(handler, "zoneItem", zoneItemAttrs); 484 485 XMLUtils.startElement(handler, "information"); 486 XMLUtils.createElement(handler, "type", type.toString()); 487 488 Object result = null; 489 if (type == ZoneType.CONTENT) 490 { 491 if (getLogger().isDebugEnabled()) 492 { 493 Content content = zoneItem.getContent(); 494 getLogger().debug("Processing content " + content.getId() + " / " + content.getPath()); 495 } 496 497 result = _saxContentZoneItem(zoneItem, handler, request); 498 } 499 else if (type == ZoneType.SERVICE) 500 { 501 if (getLogger().isDebugEnabled()) 502 { 503 getLogger().debug("Processing service " + zoneItem.getServiceId()); 504 } 505 506 result = _saxServiceZoneItem(zoneItem, handler, ex); 507 } 508 509 Source src = null; 510 if (result instanceof Source) 511 { 512 src = (Source) result; 513 } 514 else 515 { 516 ex = (Exception) result; 517 } 518 519 XMLUtils.endElement(handler, "information"); 520 521 _saxSource(handler, src, ex); 522 523 XMLUtils.endElement(handler, "zoneItem"); 524 525 // finally store the buffered data in the cache and SAX it to the pipeline 526 if (buffer != null) 527 { 528 _zoneItemCache.storePageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext, buffer); 529 buffer.toSAX(contentHandler); 530 } 531 } 532 533 // Monitor the access to this zone item. 534 _resourceAccessMonitor.addAccessRecord(zoneAccess); 535 536 // Empty content request attributes 537 request.setAttribute(Content.class.getName(), null); 538 // Empty zone item request attribute 539 request.setAttribute(ZoneItem.class.getName(), null); 540 // Empty zone item cacheable attribute 541 request.setAttribute("IsZoneItemCacheable", null); 542 543 _timeLogger.debug("Zone item {} processing time: {} ms", id, System.currentTimeMillis() - t0); 544 } 545 546 private Object _saxContentZoneItem(ZoneItem zoneItem, ContentHandler handler, Request request) throws SAXException, MalformedURLException, IOException 547 { 548 try 549 { 550 Content content = zoneItem.getContent(); 551 String metadataSetName = StringUtils.defaultString(zoneItem.getMetadataSetName(), "main"); 552 553 XMLUtils.createElement(handler, "contentId", content.getId()); 554 XMLUtils.createElement(handler, "contentName", content.getName()); 555 XMLUtils.createElement(handler, "metadataSetName", metadataSetName); 556 if (content instanceof SharedContent) 557 { 558 XMLUtils.createElement(handler, "sharedContent", "true"); 559 } 560 561 String contentTypeId = _contentTypeHelper.getContentTypeIdForRendering(content); 562 563 ContentType contentType = _contentTypeExtPt.getExtension(contentTypeId); 564 if (contentType == null) 565 { 566 return new IllegalStateException("The content type '" + contentTypeId + "' is referenced but does not exist"); 567 } 568 else 569 { 570 AttributesImpl contentTypeAttrs = new AttributesImpl(); 571 contentTypeAttrs.addCDATAAttribute("id", contentType.getId()); 572 XMLUtils.startElement(handler, "type-information", contentTypeAttrs); 573 574 contentType.getLabel().toSAX(handler, "label"); 575 contentType.getDescription().toSAX(handler, "description"); 576 577 if (contentType.getIconGlyph() != null) 578 { 579 XMLUtils.createElement(handler, "iconGlyph", contentType.getIconGlyph()); 580 } 581 if (contentType.getIconDecorator() != null) 582 { 583 XMLUtils.createElement(handler, "iconDecorator", contentType.getIconDecorator()); 584 } 585 586 if (contentType.getSmallIcon() != null) 587 { 588 XMLUtils.createElement(handler, "smallIcon", contentType.getSmallIcon()); 589 XMLUtils.createElement(handler, "mediumIcon", contentType.getMediumIcon()); 590 XMLUtils.createElement(handler, "largeIcon", contentType.getLargeIcon()); 591 } 592 593 XMLUtils.startElement(handler, "css"); 594 for (ScriptFile cssFile : contentType.getCSSFiles()) 595 { 596 _saxCSSFile(handler, cssFile); 597 } 598 XMLUtils.endElement(handler, "css"); 599 600 XMLUtils.endElement(handler, "type-information"); 601 602 String url = "cocoon://_plugins/" + contentType.getPluginName() + "/" + contentType.getId() + ".html"; 603 if (StringUtils.isNotEmpty(metadataSetName)) 604 { 605 url = url + "?metadataSetName=" + metadataSetName; 606 } 607 608 // FIXME use a context 609 request.setAttribute(Content.class.getName(), content); 610 return resolver.resolveURI(url); 611 } 612 } 613 catch (AmetysRepositoryException e) 614 { 615 return new ProcessingException("Unable to get content property", e); 616 } 617 } 618 619 private Object _saxServiceZoneItem(ZoneItem zoneItem, ContentHandler handler, Exception ex) throws SAXException, MalformedURLException, IOException 620 { 621 String serviceId = zoneItem.getServiceId(); 622 Service service = _serviceExtPt.getExtension(serviceId); 623 624 if (service == null) 625 { 626 return new ProcessingException("Unable to get service for name '" + serviceId + "'"); 627 } 628 else if (ex == null) // If an exception was caught while testing the service cacheability, do not generate 629 { 630 AttributesImpl serviceAttrs = new AttributesImpl(); 631 serviceAttrs.addCDATAAttribute("id", service.getId()); 632 XMLUtils.startElement(handler, "type-information", serviceAttrs); 633 634 service.getLabel().toSAX(handler, "label"); 635 service.getDescription().toSAX(handler, "description"); 636 637 if (service.getIconGlyph() != null) 638 { 639 XMLUtils.createElement(handler, "iconGlyph", service.getIconGlyph()); 640 } 641 if (service.getIconDecorator() != null) 642 { 643 XMLUtils.createElement(handler, "iconDecorator", service.getIconDecorator()); 644 } 645 if (service.getSmallIcon() != null) 646 { 647 XMLUtils.createElement(handler, "smallIcon", service.getSmallIcon()); 648 XMLUtils.createElement(handler, "mediumIcon", service.getMediumIcon()); 649 } 650 651 XMLUtils.startElement(handler, "css"); 652 for (ScriptFile cssFile : service.getCSSFiles()) 653 { 654 _saxCSSFile(handler, cssFile); 655 } 656 XMLUtils.endElement(handler, "css"); 657 658 XMLUtils.endElement(handler, "type-information"); 659 660 return resolver.resolveURI(service.getURL(), null, _getParameters(service, zoneItem)); 661 } 662 else 663 { 664 return ex; 665 } 666 } 667 668 private void _saxCSSFile(ContentHandler handler, ScriptFile cssFile) throws SAXException 669 { 670 AttributesImpl fileAttrs = new AttributesImpl(); 671 String debugMode = cssFile.getDebugMode(); 672 if (debugMode != null && !"all".equals(debugMode)) 673 { 674 fileAttrs.addCDATAAttribute("debug", debugMode); 675 } 676 677 if (!cssFile.isLangSpecific()) 678 { 679 String rtlMode = cssFile.getRtlMode(); 680 if (rtlMode != null && !"all".equals(rtlMode)) 681 { 682 fileAttrs.addCDATAAttribute("rtl", rtlMode); 683 } 684 685 XMLUtils.createElement(handler, "file", fileAttrs, cssFile.getPath()); 686 } 687 else 688 { 689 fileAttrs.addCDATAAttribute("lang", "true"); 690 XMLUtils.startElement(handler, "file", fileAttrs); 691 692 String defaultLang = cssFile.getDefaultLang(); 693 Map<String, String> langPaths = cssFile.getLangPaths(); 694 695 for (Entry<String, String> langPath : langPaths.entrySet()) 696 { 697 AttributesImpl langAttrs = new AttributesImpl(); 698 699 String codeLang = langPath.getKey(); 700 langAttrs.addCDATAAttribute("code", codeLang); 701 if (codeLang.equals(defaultLang)) 702 { 703 langAttrs.addCDATAAttribute("default", "true"); 704 } 705 706 XMLUtils.createElement(handler, "lang", langAttrs, langPath.getValue()); 707 } 708 709 XMLUtils.endElement(handler, "file"); 710 } 711 } 712 713 private void _saxSource(ContentHandler handler, Source src, Exception ex) throws SAXException, IOException, ProcessingException 714 { 715 if (src == null) 716 { 717 getLogger().error("Unable to display zone item", ex); 718 _saxError(handler, ex); 719 } 720 else 721 { 722 try 723 { 724 SourceUtil.toSAX(src, new IgnoreRootHandler(handler)); 725 } 726 catch (ProcessingException e) 727 { 728 getLogger().error("Unable to display zone item", e); 729 730 if (_throwException(e)) 731 { 732 throw e; 733 } 734 else 735 { 736 _saxError(handler, e.getCause()); 737 } 738 } 739 finally 740 { 741 resolver.release(src); 742 } 743 } 744 } 745 746 private String _getType(ZoneItem zoneItem) 747 { 748 ZoneType type = zoneItem.getType(); 749 750 if (type == ZoneType.CONTENT) 751 { 752 return "CONTENT"; 753 } 754 else 755 { 756 return "SERVICE:" + zoneItem.getServiceId(); 757 } 758 } 759 760 /** 761 * Get the template definition for a page 762 * @param page The page. Cannot be null. 763 * @return The template definition. Null if the page is not a container or if the template is not declared. 764 */ 765 private SkinTemplate _getTemplateDefinition(Page page) 766 { 767 if (page.getType() != PageType.CONTAINER) 768 { 769 return null; 770 } 771 772 Site site = page.getSite(); 773 String skinId = site.getSkinId(); 774 String templateName = page.getTemplate(); 775 try 776 { 777 Skin skinDef = _skinsManager.getSkin(skinId); 778 return skinDef.getTemplate(templateName); 779 } 780 catch (IllegalStateException e) 781 { 782 getLogger().error("Cannot get template definition for page '" + page.getId() + "' using template '" + templateName + "' in skin '" + skinId + "'"); 783 return null; 784 } 785 } 786 787 /** 788 * Try to inherit the zone (as it is empty) 789 * @param childPage The child page that do inherit. Cannot be null 790 * @param page The page to inherit. Cannot be null 791 * @param zoneName The zone name in the page to inherit. Cannot be null or empty 792 * @return The zone inherited or null. 793 */ 794 private Zone _inherit(Page childPage, Page page, String zoneName) 795 { 796 // The page has an existing zone at this place ? 797 if (page.hasZone(zoneName)) 798 { 799 Zone zone = page.getZone(zoneName); 800 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 801 802 // With data ? 803 if (zoneItems.iterator().hasNext()) 804 { 805 // This is it (end of the recursion) 806 return zone; 807 } 808 } 809 810 // Get the definition for the zone 811 SkinTemplateZone zoneDef; 812 813 Site site = page.getSite(); 814 String skinId = site.getSkinId(); 815 String templateName = page.getTemplate(); 816 try 817 { 818 SkinTemplate templateDef = _getTemplateDefinition(page); 819 zoneDef = templateDef.getZone(zoneName); 820 } 821 catch (IllegalStateException e) 822 { 823 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); 824 return null; 825 } 826 827 // This zone is not defined for the template 828 if (zoneDef == null) 829 { 830 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() + "'."); 831 return null; 832 } 833 834 // Support inheritance ? 835 if (!zoneDef.hasInheritance()) 836 { 837 return null; 838 } 839 840 // Get the parent page (that is a container page) 841 Page parentPage = page; 842 do 843 { 844 PagesContainer pc = parentPage.getParent(); 845 if (!(pc instanceof Page)) 846 { 847 // inheritance goes back to the root 848 return null; 849 } 850 851 parentPage = (Page) pc; 852 } 853 while (parentPage.getType() != PageType.CONTAINER); 854 855 // Get the name of the zone which will be the inheritance source 856 String parentPageTemplate = parentPage.getTemplate(); 857 String inheritanceSrc = zoneDef.getInheritance(parentPageTemplate); 858 if (inheritanceSrc == null) 859 { 860 // No inheritance for this template 861 return null; 862 } 863 864 // Finally we will inherit from the parentPage and the zone inheritanceSrc 865 return _inherit(childPage, parentPage, inheritanceSrc); 866 } 867 868 /** 869 * Test if the error has to be thrown instead of SAXing it as an error zone item. 870 * @param ex the exception. 871 * @return true to throw the exception, false to catch it and SAX it as an error zone item. 872 */ 873 private boolean _throwException(Exception ex) 874 { 875 boolean isFront = _renderingContextHandler.getRenderingContext() == RenderingContext.FRONT; 876 boolean isAuthorizationRequired = ExceptionUtils.indexOfThrowable(ex, AuthorizationRequiredException.class) > -1; 877 boolean isAccessDenied = ExceptionUtils.indexOfThrowable(ex, AccessDeniedException.class) > -1; 878 879 return isFront && (isAuthorizationRequired || isAccessDenied); 880 } 881 882 private void _saxError (ContentHandler handler, Throwable e) throws SAXException 883 { 884 XMLUtils.startElement(handler, "zone-item-error"); 885 886 XMLUtils.createElement(handler, "exception-message", e != null ? StringUtils.defaultString(e.getMessage()) : ""); 887 XMLUtils.createElement(handler, "exception-stack-trace", StringUtils.defaultString(ExceptionUtils.getFullStackTrace(e))); 888 889 XMLUtils.endElement(handler, "zone-item-error"); 890 } 891 892 private Map<String, Object> _getParameters(Service service, ZoneItem zoneItem) throws AmetysRepositoryException 893 { 894 CompositeMetadata serviceParams = zoneItem.getServiceParameters(); 895 Map<String, Object> params = new HashMap<>(); 896 897 for (ServiceParameterOrRepeater serviceParameter : service.getParameters().values()) 898 { 899 if (serviceParameter instanceof ServiceParameter) 900 { 901 String parameterName = serviceParameter.getId(); 902 903 if (serviceParams.hasMetadata(parameterName)) 904 { 905 if (serviceParams.isMultiple(parameterName)) 906 { 907 params.put(parameterName, serviceParams.getStringArray(parameterName)); 908 } 909 else 910 { 911 params.put(parameterName, serviceParams.getString(parameterName)); 912 } 913 } 914 } 915 else if (serviceParameter instanceof ServiceParameterRepeater) 916 { 917 ServiceParameterRepeater repeater = (ServiceParameterRepeater) serviceParameter; 918 919 List<Map<String, Object>> repeaterValues = _getRepeaterValues(repeater, serviceParams); 920 921 if (repeaterValues != null) 922 { 923 params.put(repeater.getId(), repeaterValues); 924 } 925 } 926 } 927 928 return params; 929 } 930 931 /** 932 * Get the values of a repeater in a service instance. 933 * @param repeater the repeater definition. 934 * @param serviceParams the service root parameter holder. 935 * @return the full repeater values as a List of Map<parameterId, value> or null if the repeater is not valued. 936 */ 937 protected List<Map<String, Object>> _getRepeaterValues(ServiceParameterRepeater repeater, CompositeMetadata serviceParams) 938 { 939 List<Map<String, Object>> repeaterValues = null; 940 941 String repeaterName = repeater.getId(); 942 if (serviceParams.hasMetadata(repeaterName) && serviceParams.getType(repeaterName) == MetadataType.COMPOSITE) 943 { 944 repeaterValues = new ArrayList<>(); 945 946 CompositeMetadata repeaterMeta = serviceParams.getCompositeMetadata(repeaterName); 947 948 String[] entryNames = repeaterMeta.getMetadataNames(); 949 Arrays.sort(entryNames, MetadataManager.REPEATER_ENTRY_COMPARATOR); 950 951 // Process each entry. 952 for (String entryName : entryNames) 953 { 954 if (repeaterMeta.getType(entryName) == MetadataType.COMPOSITE) 955 { 956 CompositeMetadata entryParams = repeaterMeta.getCompositeMetadata(entryName); 957 958 Map<String, Object> entryValues = new HashMap<>(); 959 repeaterValues.add(entryValues); 960 961 // Process entry values. 962 for (String parameterName : repeater.getChildrenParameters().keySet()) 963 { 964 if (entryParams.hasMetadata(parameterName)) 965 { 966 if (entryParams.isMultiple(parameterName)) 967 { 968 entryValues.put(parameterName, entryParams.getStringArray(parameterName)); 969 } 970 else 971 { 972 entryValues.put(parameterName, entryParams.getString(parameterName)); 973 } 974 } 975 } 976 } 977 } 978 } 979 980 return repeaterValues; 981 } 982}