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.cms.source;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.avalon.framework.context.Context;
023import org.apache.avalon.framework.context.ContextException;
024import org.apache.avalon.framework.context.Contextualizable;
025import org.apache.avalon.framework.logger.AbstractLogEnabled;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.cocoon.components.ContextHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.commons.lang3.tuple.Pair;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceResolver;
034
035import org.ametys.cms.content.GetContentAction;
036import org.ametys.cms.contenttype.ContentTypeDescriptor;
037import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
038import org.ametys.cms.contenttype.ContentTypesHelper;
039import org.ametys.cms.contenttype.DynamicContentTypeDescriptorExtentionPoint;
040
041/**
042 * Default implementation of a {@link ContentView}
043 * Will first look in directory context://WEB-INF/stylesheets/content/article/article-[view].xsl
044 * And if the file does not exist will search in plugin:[currentPluginName]://stylesheets/content/article/article-[view].xsl
045 */
046public class DefaultContentView extends AbstractLogEnabled implements ContentView, Serviceable, Contextualizable
047{
048    /** The source resolver */
049    protected SourceResolver _resolver;
050    /** Helper for content types */
051    protected ContentTypesHelper _contentTypesHelper;
052    /** The content types extension point */
053    protected ContentTypeExtensionPoint _contentTypeEP;
054    /** The dynamic content type descriptior extension point */
055    protected DynamicContentTypeDescriptorExtentionPoint _dynamicContentTypeDescriptorEP;
056    
057    
058    /** Context to get full content type ID from request */
059    protected Context _context;
060    
061    public void contextualize(Context context) throws ContextException
062    {
063        _context = context;
064    }
065    
066    @Override
067    public void service(ServiceManager manager) throws ServiceException
068    {
069        _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
070        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
071        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
072        _dynamicContentTypeDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) manager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE);
073    }
074    
075    @Override
076    public Source getSource(String location, String contentType, String view, String format, String pluginName) throws IOException
077    {
078        Request request = ContextHelper.getRequest(_context);
079        String fullContentTypeId = (String) request.getAttribute(GetContentAction.RESULT_CONTENTTYPE);
080        
081        for (String s : _getSourceURIs(contentType, fullContentTypeId, view, format, pluginName))
082        {
083            try
084            {
085                Source src = _resolver.resolveURI(s);
086                if (!src.exists())
087                {
088                    if (getLogger().isDebugEnabled())
089                    {
090                        getLogger().debug("Failed to find a stylesheet at '" + s + "'.");
091                    }
092                }
093                else
094                {
095                    return src;
096                }
097            }
098            catch (IOException e)
099            {
100                if (getLogger().isDebugEnabled())
101                {
102                    getLogger().debug("Resolving protocol failed for resolving '" + s + "'.");
103                }
104            }
105        }
106        
107        // should never occur because of the default stylesheet
108        throw new IOException("Can't find a stylesheet for: " + location);
109    }
110    
111    /**
112     * Returns the ordered list of URI to be tested to find a stylesheet to render the Content.
113     * @param contentTypeAlias the content type alias for rendering
114     * @param contentTypeId the content type full ID
115     * @param view the content view
116     * @param format the format of the output (html, pdf, ...)
117     * @param pluginName the current plugin name
118     * @return a list of URIs
119     */
120    protected List<String> _getSourceURIs(String contentTypeAlias, String contentTypeId, String view, String format, String pluginName)
121    {
122        String formatSuffix = "html".equals(format) ? "" : "2" + format;
123        
124        ArrayList<String> result = new ArrayList<>();
125
126        ContentTypeDescriptor contentType = _contentTypeEP.getExtension(contentTypeId);
127        if (contentType != null)
128        {
129            result.addAll(_getSourceURIsForContentType(contentTypeAlias, contentTypeId, view, formatSuffix));
130            result.addAll(_getSourceURIsForSuperTypes(contentTypeAlias, contentTypeId, view, formatSuffix));
131        }
132        else
133        {
134            contentType = _dynamicContentTypeDescriptorEP.getExtension(contentTypeId);
135            if (contentType != null)
136            {
137                result.addAll(_getSourceURIsForDynamicContentType(contentTypeAlias, view, formatSuffix, contentType.getPluginName()));
138            }
139        }
140        
141        // then a default stylesheet
142        result.addAll(_getDefaultSourceURIsForContentType(contentType, pluginName, formatSuffix));
143        
144        // and finally a dynamically constructed XSL, based on the model
145        result.add("cocoon://_plugins/cms/default-content/" + format + "-" + view + ".xsl");
146        
147        return result;
148    }
149
150    /**
151     * Get the URIs of default stylesheets
152     * @param contentType The content type
153     * @param pluginName The current plugin name
154     * @param formatSuffix the format of the output (html, pdf, ...)
155     * @return a list of URIs
156     */
157    protected List<String> _getDefaultSourceURIsForContentType(ContentTypeDescriptor contentType, String pluginName, String formatSuffix)
158    {
159        List<String> result = new ArrayList<>();
160        
161        if (contentType != null && !contentType.getPluginName().equals(pluginName))
162        {
163            result.add("plugin:" + contentType.getPluginName() + "://stylesheets/default-content" + formatSuffix + ".xsl");
164        }
165        
166        if (pluginName != null)
167        {
168            result.add("plugin:" + pluginName + "://stylesheets/default-content" + formatSuffix + ".xsl");
169        }
170        
171        result.addAll(_getDefaultSourceURIs(pluginName, formatSuffix));
172        
173        return result;
174    }
175    
176    /**
177     * Get the URIs of default stylesheets
178     * @param pluginName The current plugin name
179     * @param formatSuffix the format of the output (html, pdf, ...)
180     * @return a list of URIs
181     */
182    protected List<String> _getDefaultSourceURIs(String pluginName, String formatSuffix)
183    {
184        List<String> result = new ArrayList<>();
185        
186        if (!"cms".equals(pluginName))
187        {
188            result.add("plugin:cms://stylesheets/default-content" + formatSuffix + ".xsl");
189        }
190        return result;
191    }
192    
193    /**
194     * Get source for a given content type
195     * @param contentTypeAlias the content type alias for rendering
196     * @param contentTypeId the content type full ID
197     * @param view the content view
198     * @param formatSuffix the format of the output (html, pdf, ...)
199     * @return the list
200     */
201    protected List<String> _getSourceURIsForSuperTypes(String contentTypeAlias, String contentTypeId, String view, String formatSuffix)
202    {
203        ArrayList<String> result = new ArrayList<>();
204        
205        Pair<String[], String[]> supertypeIds = _contentTypesHelper.getSupertypeIds(contentTypeId);
206        
207        final String dynamicContentTypeId = _contentTypesHelper.getDynamicContentTypeId(supertypeIds.getLeft(), supertypeIds.getRight());
208        if (dynamicContentTypeId != null)
209        {
210            final String dynamicContentTypePluginName = _contentTypesHelper.getDynamicContentTypePlugin(supertypeIds.getLeft(), supertypeIds.getRight());
211            result.addAll(_getSourceURIsForDynamicContentType(dynamicContentTypeId, view, formatSuffix, dynamicContentTypePluginName));
212        }
213        
214        for (String superTypeId : supertypeIds.getLeft())
215        {
216            result.addAll(_getSourceURIsForContentType(superTypeId, superTypeId, view, formatSuffix));
217        }
218        
219        for (String superTypeId : supertypeIds.getLeft())
220        {
221            result.addAll(_getSourceURIsForSuperTypes(superTypeId, superTypeId, view, formatSuffix));
222        }
223        
224        return result;
225    }
226    
227    /**
228     * get source for a dynamic content type
229     * @param contentTypeAlias the content type alias for rendering
230     * @param view the content view
231     * @param formatSuffix the format of the output (html, pdf, ...)
232     * @param pluginName the plugin name
233     * @return the list
234     */
235    protected List<String> _getSourceURIsForDynamicContentType(String contentTypeAlias, String view, String formatSuffix, String pluginName)
236    {
237        ArrayList<String> result = new ArrayList<>();
238        
239        result.add("context://WEB-INF/param/content-types/_dynamic/" + pluginName + "/stylesheets/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + "-" + view + ".xsl");
240        result.add("context://WEB-INF/param/content-types/_dynamic/" + pluginName + "/stylesheets/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + ".xsl");
241        
242        return result;
243    }
244
245    /**
246     * get source for a non dynamic content type
247     * @param contentTypeAlias the content type alias for rendering
248     * @param contentTypeId the content type full ID
249     * @param view the content view
250     * @param formatSuffix the format of the output (html, pdf, ...)
251     * @return the list
252     */
253    protected List<String> _getSourceURIsForContentType(String contentTypeAlias, String contentTypeId, String view, String formatSuffix)
254    {
255        ArrayList<String> result = new ArrayList<>();
256        
257        String pluginName = _contentTypesHelper.getPluginName(contentTypeId);
258        
259        // first look in the filesystem (case of automatic content types)
260        result.add("context://WEB-INF/param/content-types/" + pluginName + "/stylesheets/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + "-" + view + ".xsl");
261        result.add("context://WEB-INF/param/content-types/" + pluginName + "/stylesheets/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + ".xsl");
262        
263        // then look in the plugin
264        result.add("plugin:" + pluginName + "://stylesheets/content/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + "-" + view + ".xsl");
265        result.add("plugin:" + pluginName + "://stylesheets/content/" + contentTypeAlias + "/" + contentTypeAlias + formatSuffix + ".xsl");
266        
267        return result;
268    }
269}