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