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