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