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