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.util.Map; 020import java.util.Map.Entry; 021import java.util.Objects; 022import java.util.Set; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.cocoon.ProcessingException; 027import org.apache.cocoon.environment.ObjectModelHelper; 028import org.apache.cocoon.environment.Request; 029import org.apache.cocoon.generation.ServiceableGenerator; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.apache.cocoon.xml.XMLUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.lang3.exception.ExceptionUtils; 034import org.xml.sax.ContentHandler; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.content.GetContentAction; 038import org.ametys.cms.contenttype.ContentTypeDescriptor; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.contenttype.ContentTypesHelper; 041import org.ametys.cms.contenttype.DynamicContentTypeDescriptorExtentionPoint; 042import org.ametys.cms.repository.Content; 043import org.ametys.core.DevMode; 044import org.ametys.core.DevMode.DEVMODE; 045import org.ametys.core.right.RightManager; 046import org.ametys.core.right.RightManager.RightResult; 047import org.ametys.core.ui.ClientSideElement.ScriptFile; 048import org.ametys.core.util.I18nUtils; 049import org.ametys.plugins.core.ui.ObfuscatedException; 050import org.ametys.plugins.repository.AmetysObjectIterable; 051import org.ametys.plugins.repository.AmetysRepositoryException; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.web.WebConstants; 054import org.ametys.web.repository.content.SharedContent; 055import org.ametys.web.repository.page.ContentTypesAssignmentHandler; 056import org.ametys.web.repository.page.ServicesAssignmentHandler; 057import org.ametys.web.repository.page.Zone; 058import org.ametys.web.repository.page.ZoneItem; 059import org.ametys.web.repository.page.ZoneItem.ZoneType; 060import org.ametys.web.repository.site.Site; 061import org.ametys.web.repository.sitemap.Sitemap; 062import org.ametys.web.service.Service; 063import org.ametys.web.service.ServiceExtensionPoint; 064import org.ametys.web.skin.Skin; 065import org.ametys.web.skin.SkinTemplate; 066import org.ametys.web.skin.SkinTemplateZone; 067import org.ametys.web.skin.SkinsManager; 068 069/** 070 * Emulator of {@link PageGenerator} 071 */ 072public class SitemapPageGenerator extends ServiceableGenerator 073{ 074 private ServiceExtensionPoint _serviceExtPt; 075 private ContentTypeExtensionPoint _contentTypeExtPt; 076 private SkinsManager _skinsManager; 077 private ContentTypesHelper _contentTypeHelper; 078 private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP; 079 private RightManager _rightManager; 080 081 /** The content type assignment handler. */ 082 private ContentTypesAssignmentHandler _cTypeAssignmentHandler; 083 084 /** The service assignment handler. */ 085 private ServicesAssignmentHandler _serviceAssignmentHandler; 086 private I18nUtils _i18nUtils; 087 088 @Override 089 public void service(ServiceManager serviceManager) throws ServiceException 090 { 091 super.service(serviceManager); 092 _serviceExtPt = (ServiceExtensionPoint) serviceManager.lookup(ServiceExtensionPoint.ROLE); 093 _contentTypeExtPt = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 094 _skinsManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE); 095 _cTypeAssignmentHandler = (ContentTypesAssignmentHandler) serviceManager.lookup(ContentTypesAssignmentHandler.ROLE); 096 _serviceAssignmentHandler = (ServicesAssignmentHandler) serviceManager.lookup(ServicesAssignmentHandler.ROLE); 097 _contentTypeHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 098 _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) serviceManager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); 099 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 100 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 101 } 102 103 @Override 104 public void generate() throws IOException, SAXException, ProcessingException 105 { 106 Request request = ObjectModelHelper.getRequest(objectModel); 107 108 Sitemap sitemap = (Sitemap) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP); 109 110 String siteName = sitemap.getSiteName(); 111 112 if (sitemap.getTemplate() == null) 113 { 114 throw new IllegalStateException("Cannot invoke the SitemapPageGenerator on a Sitemap without a template"); 115 } 116 117 contentHandler.startDocument(); 118 AttributesImpl attrs = new AttributesImpl(); 119 attrs.addCDATAAttribute("title", sitemap.getName()); 120 attrs.addCDATAAttribute("long-title", sitemap.getName()); 121 attrs.addCDATAAttribute("id", sitemap.getId()); 122 XMLUtils.startElement(contentHandler, "page", attrs); 123 124 try 125 { 126 XMLUtils.startElement(contentHandler, "metadata"); 127 sitemap.dataToSAX(contentHandler); 128 XMLUtils.endElement(contentHandler, "metadata"); 129 } 130 catch (AmetysRepositoryException e) 131 { 132 throw new ProcessingException("Unable to SAX sitemap metadata", e); 133 } 134 135 AttributesImpl pcattrs = new AttributesImpl(); 136 pcattrs.addCDATAAttribute("modifiable", "true"); 137 pcattrs.addCDATAAttribute("moveable", "false"); 138 XMLUtils.startElement(contentHandler, "pageContents", pcattrs); 139 140 try 141 { 142 // Iterate on existing zones 143 for (Zone zone : sitemap.getZones()) 144 { 145 String zoneName = zone.getName(); 146 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 147 148 _saxZone(sitemap, zoneName, zoneItems, siteName); 149 } 150 151 // Iterate on defined zone (that are not existing) 152 SkinTemplate skinTemplate = _getTemplateDefinition(sitemap); 153 if (skinTemplate != null) 154 { 155 for (SkinTemplateZone zoneDef : skinTemplate.getZones().values()) 156 { 157 if (!sitemap.hasZone(zoneDef.getId())) 158 { 159 _saxZone(sitemap, zoneDef.getId(), null, siteName); 160 } 161 } 162 } 163 } 164 catch (AmetysRepositoryException ex) 165 { 166 throw new ProcessingException("Unable to get Content", ex); 167 } 168 169 XMLUtils.endElement(contentHandler, "pageContents"); 170 XMLUtils.endElement(contentHandler, "page"); 171 contentHandler.endDocument(); 172 } 173 174 /** 175 * Sax a zone 176 * @param sitemap The page 177 * @param zoneName The zone in the page to sax 178 * @param zoneItems The items of the zone or null 179 * @param site the site's name 180 * @throws SAXException if an error occurs while saxing 181 * @throws IOException if an I/O exception occurs 182 * @throws ProcessingException if an error occurs 183 */ 184 private void _saxZone(Sitemap sitemap, String zoneName, AmetysObjectIterable<? extends ZoneItem> zoneItems, String site) throws SAXException, IOException, ProcessingException 185 { 186 AmetysObjectIterable<? extends ZoneItem> localZoneItems = zoneItems; 187 188 AttributesImpl zoneAttrs = new AttributesImpl(); 189 zoneAttrs.addCDATAAttribute("name", zoneName); 190 191 Request request = ObjectModelHelper.getRequest(objectModel); 192 request.setAttribute(Zone.class.getName(), zoneName); 193 194 XMLUtils.startElement(contentHandler, "zone", zoneAttrs); 195 196 _saxAvailableContentTypes(sitemap, zoneName); 197 _saxAvailableServices(sitemap, zoneName); 198 199 _saxZoneItems(localZoneItems); 200 201 XMLUtils.endElement(contentHandler, "zone"); 202 request.setAttribute(Zone.class.getName(), null); 203 204 } 205 206 /** 207 * Generate the list of available services for the given zone. 208 * @param sitemap the page. 209 * @param zoneName the zone name in the page. 210 * @throws SAXException if something goes wrong when saxing the available services 211 */ 212 private void _saxAvailableServices(Sitemap sitemap, String zoneName) throws SAXException 213 { 214 Set<String> services = _serviceAssignmentHandler.getAvailableServices(sitemap, zoneName); 215 216 XMLUtils.startElement(contentHandler, "available-services"); 217 218 for (String service : services) 219 { 220 AttributesImpl attrs = new AttributesImpl(); 221 attrs.addCDATAAttribute("id", service); 222 XMLUtils.createElement(contentHandler, "service", attrs); 223 } 224 225 XMLUtils.endElement(contentHandler, "available-services"); 226 } 227 228 /** 229 * Generate the list of available content types for the given zone. 230 * @param sitemap the page. 231 * @param zoneName the zone name in the page. 232 * @throws SAXException if something goes wrong when saxing the available content types 233 */ 234 private void _saxAvailableContentTypes(Sitemap sitemap, String zoneName) throws SAXException 235 { 236 Set<String> cTypes = _cTypeAssignmentHandler.getAvailableContentTypes(sitemap, zoneName, true); 237 238 XMLUtils.startElement(contentHandler, "available-content-types"); 239 240 for (String cType : cTypes) 241 { 242 AttributesImpl attrs = new AttributesImpl(); 243 attrs.addCDATAAttribute("id", cType); 244 XMLUtils.createElement(contentHandler, "content-type", attrs); 245 } 246 247 XMLUtils.endElement(contentHandler, "available-content-types"); 248 } 249 250 /** 251 * Sax zone items 252 * @param zoneItems The zone items to sax 253 * @throws SAXException if an error occurs while saxing 254 * @throws IOException if an I/O exception occurs 255 * @throws ProcessingException if an error occurs 256 */ 257 private void _saxZoneItems(AmetysObjectIterable< ? extends ZoneItem> zoneItems) throws SAXException, IOException, ProcessingException 258 { 259 if (zoneItems == null) 260 { 261 return; 262 } 263 264 Request request = ObjectModelHelper.getRequest(objectModel); 265 266 for (ZoneItem zoneItem : zoneItems) 267 { 268 _saxZoneItem(request, zoneItem); 269 } 270 } 271 272 private void _saxZoneItem(Request request, ZoneItem zoneItem) throws SAXException 273 { 274 String id = zoneItem.getId(); 275 ZoneType type = zoneItem.getType(); 276 277 request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM, zoneItem); 278 279 AttributesImpl zoneItemAttrs = new AttributesImpl(); 280 zoneItemAttrs.addCDATAAttribute("id", id); 281 282 Object zoneItemObject = null; 283 284 ContentHandler handler = contentHandler; 285 286 XMLUtils.startElement(handler, "zoneItem", zoneItemAttrs); 287 288 XMLUtils.startElement(handler, "information"); 289 XMLUtils.createElement(handler, "type", type.toString()); 290 291 if (type == ZoneType.CONTENT) 292 { 293 if (getLogger().isDebugEnabled()) 294 { 295 Content content = zoneItem.getContent(); 296 getLogger().debug("Processing content " + content.getId() + " / " + content.getPath()); 297 } 298 299 zoneItemObject = _saxContentZoneItem(zoneItem, handler, request); 300 } 301 else if (type == ZoneType.SERVICE) 302 { 303 if (getLogger().isDebugEnabled()) 304 { 305 getLogger().debug("Processing service " + zoneItem.getServiceId()); 306 } 307 308 zoneItemObject = _saxServiceZoneItem(zoneItem, handler); 309 } 310 311 XMLUtils.endElement(handler, "information"); 312 313 if (zoneItemObject instanceof Exception ex) 314 { 315 Throwable e = _obfuscate(ex); 316 if (e instanceof ObfuscatedException obfuscatedException) 317 { 318 obfuscatedException.reveal(); 319 getLogger().error("Unable to display zone item", ex); 320 obfuscatedException.obfuscate(); 321 } 322 else 323 { 324 getLogger().error("Unable to display zone item", ex); 325 } 326 327 328 _saxError(handler, ex); 329 } 330 else if (zoneItemObject instanceof Service service) 331 { 332 _saxHTMLTitle(_i18nUtils.translate(service.getLabel())); 333 } 334 else if (zoneItemObject instanceof Content content) 335 { 336 _saxHTMLTitle(content.getTitle()); 337 } 338 339 XMLUtils.endElement(handler, "zoneItem"); 340 341 // Empty content request attributes 342 request.setAttribute(Content.class.getName(), null); 343 // Empty zone item request attribute 344 request.setAttribute(WebConstants.REQUEST_ATTR_ZONEITEM, null); 345 } 346 347 private void _saxHTMLTitle(String title) throws SAXException 348 { 349 XMLUtils.startElement(contentHandler, "html"); 350 XMLUtils.startElement(contentHandler, "head"); 351 XMLUtils.createElement(contentHandler, "title", title); 352 XMLUtils.endElement(contentHandler, "head"); 353 XMLUtils.startElement(contentHandler, "body"); 354 AttributesImpl attrs = new AttributesImpl(); 355 attrs.addCDATAAttribute("class", "ametys-richtext-title-1"); 356 XMLUtils.createElement(contentHandler, "h1", attrs, title); 357 XMLUtils.createElement(contentHandler, "p", _i18nUtils.translate(new I18nizableText("plugin.web", "PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_USE_PREVIEW"))); 358 XMLUtils.endElement(contentHandler, "body"); 359 XMLUtils.endElement(contentHandler, "html"); 360 } 361 362 private Object _saxContentZoneItem(ZoneItem zoneItem, ContentHandler handler, Request request) throws SAXException 363 { 364 try 365 { 366 Content content = zoneItem.getContent(); 367 String viewName = Objects.toString(zoneItem.getViewName(), "main"); 368 369 XMLUtils.createElement(handler, "contentId", content.getId()); 370 XMLUtils.createElement(handler, "contentName", content.getName()); 371 XMLUtils.createElement(handler, "metadataSetName", viewName); 372 if (content instanceof SharedContent) 373 { 374 XMLUtils.createElement(handler, "sharedContent", "true"); 375 } 376 377 String contentTypeId = _contentTypeHelper.getContentTypeIdForRendering(content); 378 379 ContentTypeDescriptor contentType = _contentTypeExtPt.getExtension(contentTypeId); 380 if (contentType == null) 381 { 382 contentType = _dynamicCTDescriptorEP.getExtension(contentTypeId); 383 } 384 385 if (contentType == null) 386 { 387 return new IllegalStateException("The content type '" + contentTypeId + "' is referenced but does not exist"); 388 } 389 else 390 { 391 AttributesImpl contentTypeAttrs = new AttributesImpl(); 392 contentTypeAttrs.addCDATAAttribute("id", contentType.getId()); 393 XMLUtils.startElement(handler, "type-information", contentTypeAttrs); 394 395 contentType.getLabel().toSAX(handler, "label"); 396 contentType.getDescription().toSAX(handler, "description"); 397 398 if (contentType.getIconGlyph() != null) 399 { 400 XMLUtils.createElement(handler, "iconGlyph", contentType.getIconGlyph()); 401 } 402 if (contentType.getIconDecorator() != null) 403 { 404 XMLUtils.createElement(handler, "iconDecorator", contentType.getIconDecorator()); 405 } 406 407 if (contentType.getSmallIcon() != null) 408 { 409 XMLUtils.createElement(handler, "smallIcon", contentType.getSmallIcon()); 410 XMLUtils.createElement(handler, "mediumIcon", contentType.getMediumIcon()); 411 XMLUtils.createElement(handler, "largeIcon", contentType.getLargeIcon()); 412 } 413 414 XMLUtils.startElement(handler, "css"); 415 for (ScriptFile cssFile : contentType.getCSSFiles()) 416 { 417 _saxCSSFile(handler, cssFile); 418 } 419 XMLUtils.endElement(handler, "css"); 420 421 XMLUtils.endElement(handler, "type-information"); 422 423 // FIXME use a context 424 request.setAttribute(Content.class.getName(), content); 425 request.setAttribute(GetContentAction.RESULT_CONTENTTYPE, contentTypeId); 426 427 return content; 428 } 429 } 430 catch (AmetysRepositoryException e) 431 { 432 return new ProcessingException("Unable to get content property", e); 433 } 434 } 435 436 private Object _saxServiceZoneItem(ZoneItem zoneItem, ContentHandler handler) throws SAXException 437 { 438 String serviceId = zoneItem.getServiceId(); 439 Service service = _serviceExtPt.getExtension(serviceId); 440 441 if (service == null) 442 { 443 return new ProcessingException("Unable to get service for name '" + serviceId + "'"); 444 } 445 else 446 { 447 AttributesImpl serviceAttrs = new AttributesImpl(); 448 serviceAttrs.addCDATAAttribute("id", service.getId()); 449 XMLUtils.startElement(handler, "type-information", serviceAttrs); 450 451 service.getLabel().toSAX(handler, "label"); 452 service.getDescription().toSAX(handler, "description"); 453 454 if (service.getIconGlyph() != null) 455 { 456 XMLUtils.createElement(handler, "iconGlyph", service.getIconGlyph()); 457 } 458 if (service.getIconDecorator() != null) 459 { 460 XMLUtils.createElement(handler, "iconDecorator", service.getIconDecorator()); 461 } 462 if (service.getSmallIcon() != null) 463 { 464 XMLUtils.createElement(handler, "smallIcon", service.getSmallIcon()); 465 XMLUtils.createElement(handler, "mediumIcon", service.getMediumIcon()); 466 } 467 468 XMLUtils.startElement(handler, "css"); 469 for (ScriptFile cssFile : service.getCSSFiles()) 470 { 471 _saxCSSFile(handler, cssFile); 472 } 473 XMLUtils.endElement(handler, "css"); 474 475 XMLUtils.endElement(handler, "type-information"); 476 477 return service; 478 } 479 } 480 481 private void _saxCSSFile(ContentHandler handler, ScriptFile cssFile) throws SAXException 482 { 483 AttributesImpl fileAttrs = new AttributesImpl(); 484 if (!cssFile.isLangSpecific()) 485 { 486 String rtlMode = cssFile.getRtlMode(); 487 if (rtlMode != null && !"all".equals(rtlMode)) 488 { 489 fileAttrs.addCDATAAttribute("rtl", rtlMode); 490 } 491 492 XMLUtils.createElement(handler, "file", fileAttrs, cssFile.getPath()); 493 } 494 else 495 { 496 fileAttrs.addCDATAAttribute("lang", "true"); 497 XMLUtils.startElement(handler, "file", fileAttrs); 498 499 String defaultLang = cssFile.getDefaultLang(); 500 Map<String, String> langPaths = cssFile.getLangPaths(); 501 502 for (Entry<String, String> langPath : langPaths.entrySet()) 503 { 504 AttributesImpl langAttrs = new AttributesImpl(); 505 506 String codeLang = langPath.getKey(); 507 langAttrs.addCDATAAttribute("code", codeLang); 508 if (codeLang.equals(defaultLang)) 509 { 510 langAttrs.addCDATAAttribute("default", "true"); 511 } 512 513 XMLUtils.createElement(handler, "lang", langAttrs, langPath.getValue()); 514 } 515 516 XMLUtils.endElement(handler, "file"); 517 } 518 } 519 520 /** 521 * Get the template definition for a page 522 * @param sitemap The page. Cannot be null. 523 * @return The template definition. Null if the page is not a container or if the template is not declared. 524 */ 525 private SkinTemplate _getTemplateDefinition(Sitemap sitemap) 526 { 527 Site site = sitemap.getSite(); 528 String skinId = site.getSkinId(); 529 String templateName = sitemap.getTemplate(); 530 try 531 { 532 Skin skinDef = _skinsManager.getSkin(skinId); 533 return skinDef.getTemplate(templateName); 534 } 535 catch (IllegalStateException e) 536 { 537 getLogger().error("Cannot get template definition for sitemap '" + sitemap.getId() + "' using template '" + templateName + "' in skin '" + skinId + "'"); 538 return null; 539 } 540 } 541 542 private void _saxError (ContentHandler handler, Throwable e) throws SAXException 543 { 544 XMLUtils.startElement(handler, "zone-item-error"); 545 546 XMLUtils.createElement(handler, "exception-message", e != null ? StringUtils.defaultString(e.getMessage()) : ""); 547 XMLUtils.createElement(handler, "exception-stack-trace", StringUtils.defaultString(ExceptionUtils.getStackTrace(e))); 548 549 XMLUtils.endElement(handler, "zone-item-error"); 550 } 551 552 private Throwable _obfuscate(Throwable throwable) 553 { 554 if (!DEVMODE.PRODUCTION.equals(DevMode.getDeveloperMode()) // Do not read dev mode from request to prevent override from request param 555 || _rightManager.currentUserHasRight("Runtime_Rights_Admin_Access", "/admin").equals(RightResult.RIGHT_ALLOW)) 556 { 557 return throwable; 558 } 559 else 560 { 561 return ObfuscatedException.obfuscate(throwable); 562 } 563 564 } 565 566}