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