001/*
002 *  Copyright 2012 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 */
016
017package org.ametys.cms.transformation.xslt;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Locale;
023import java.util.Map;
024
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.logger.LogEnabled;
028import org.apache.avalon.framework.logger.Logger;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.lang3.ArrayUtils;
035import org.w3c.dom.Element;
036import org.w3c.dom.Node;
037import org.w3c.dom.NodeList;
038
039import org.ametys.cms.content.ContentHelper;
040import org.ametys.cms.contenttype.ContentType;
041import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
042import org.ametys.cms.repository.Content;
043import org.ametys.cms.tag.CMSTag;
044import org.ametys.cms.tag.Tag;
045import org.ametys.cms.tag.TagProviderExtensionPoint;
046import org.ametys.cms.transformation.dom.TagElement;
047import org.ametys.core.util.dom.AmetysNodeList;
048import org.ametys.core.util.dom.EmptyElement;
049import org.ametys.core.util.dom.StringElement;
050import org.ametys.plugins.explorer.resources.Resource;
051import org.ametys.plugins.explorer.resources.ResourceCollection;
052import org.ametys.plugins.explorer.resources.dom.ResourceCollectionElement;
053import org.ametys.plugins.repository.AmetysObjectResolver;
054import org.ametys.plugins.repository.AmetysRepositoryException;
055import org.ametys.plugins.repository.UnknownAmetysObjectException;
056import org.ametys.plugins.repository.metadata.CompositeMetadata;
057import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType;
058import org.ametys.plugins.repository.metadata.UnknownMetadataException;
059import org.ametys.plugins.repository.version.VersionAwareAmetysObject;
060
061/**
062 * Helper component to be used from XSL stylesheets.
063 */
064public class AmetysXSLTHelper extends org.ametys.core.util.AmetysXSLTHelper implements LogEnabled
065{
066    /** The Ametys object resolver */
067    protected static AmetysObjectResolver _ametysObjectResolver;
068    /** The content types extension point */
069    protected static ContentTypeExtensionPoint _cTypeExtensionPoint;
070    /** The tags provider */
071    protected static TagProviderExtensionPoint _tagProviderExtPt;
072    /** Helper for content */
073    protected static ContentHelper _contentHelper;
074    /** The avalon context */
075    protected static Context _context;
076    /** The logger */
077    protected static Logger _logger;
078    
079    
080    @Override
081    public void contextualize(Context context) throws ContextException
082    {
083        super.contextualize(context);
084        _context = context;
085    }
086    
087    @Override
088    public void enableLogging(Logger logger)
089    {
090        _logger = logger;
091    }
092    
093    @Override
094    public void service(ServiceManager manager) throws ServiceException
095    {
096        _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
097        _cTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
098        _tagProviderExtPt = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE);
099        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
100    }
101    
102    /* ------------------------ */
103    /*      Content methods     */
104    /* ------------------------ */
105    
106    /**
107     * Get the content types of a content
108     * @param contentId The content id
109     * @return The content type or empty if the content does not exist
110     */
111    public static NodeList contentTypes(String contentId)
112    {
113        ArrayList<StringElement> contentTypes = new ArrayList<>();
114        
115        try
116        {
117            Content content = _ametysObjectResolver.resolveById(contentId);
118            
119            try
120            {
121                for (String id : content.getTypes())
122                {
123                    contentTypes.add(new StringElement("content-type", "id", id));
124                }
125            }
126            catch (AmetysRepositoryException e)
127            {
128                _logger.error("Can not get type of content with id '" + contentId + "'", e);
129            }
130        }
131        catch (UnknownAmetysObjectException e)
132        {
133            _logger.error("Can not get type of content with id '" + contentId + "'", e);
134        }
135        
136        return new AmetysNodeList(contentTypes);
137    }
138    
139    /**
140     * Get the mixins of a content
141     * @param contentId The content id
142     * @return The content type or empty if the content does not exist
143     */
144    public static NodeList contentMixinTypes(String contentId)
145    {
146        ArrayList<StringElement> contentTypes = new ArrayList<>();
147        
148        try
149        {
150            Content content = _ametysObjectResolver.resolveById(contentId);
151            
152            try
153            {
154                for (String id : content.getMixinTypes())
155                {
156                    contentTypes.add(new StringElement("mixin", "id", id));
157                }
158            }
159            catch (AmetysRepositoryException e)
160            {
161                _logger.error("Can not get type of content with id '" + contentId + "'", e);
162            }
163        }
164        catch (UnknownAmetysObjectException e)
165        {
166            _logger.error("Can not get type of content with id '" + contentId + "'", e);
167        }
168        
169        return new AmetysNodeList(contentTypes);
170    }
171    
172    /**
173     * Determines if the content of given id is a entry of reference table
174     * @param contentId the content id
175     * @return true if the content type is a reference table
176     */
177    public static boolean isReferenceTableContent(String contentId)
178    {
179        try
180        {
181            Content content = _ametysObjectResolver.resolveById(contentId);
182            return _contentHelper.isReferenceTable(content);
183        }
184        catch (UnknownAmetysObjectException e)
185        {
186            _logger.error("Can not get type of unknown content with id '" + contentId + "'", e);
187            return false;
188        }
189    }
190    
191    /**
192     * Returns the current language for rendering.
193     * @return the current language for rendering.
194     */
195    public static String lang()
196    {
197        Request request = ContextHelper.getRequest(_context);
198        return (String) request.getAttribute("renderingLanguage");
199    }
200    
201    /**
202     * Get the metadata of a content
203     * @param contentId The content id
204     * @param metadataName The metadata name (/ for composite)
205     * @param lang The language for localized metadata. Can be null to get the current language.
206     * @return The name or empty if the metadata or the content does not exist
207     */
208    public static String contentMetadata(String contentId, String metadataName, String lang)
209    {
210        try
211        {
212            Content content = _ametysObjectResolver.resolveById(contentId);
213            try
214            {
215                Locale locale = StringUtils.isEmpty(lang) ? null : new Locale(lang);
216                return _getMetadata(content.getMetadataHolder(), metadataName, locale);
217            }
218            catch (UnknownMetadataException e)
219            {
220                _logger.debug("Can not get metadata '" + metadataName + "' on content with id '" + contentId + "'", e);
221                return "";
222            }
223        }
224        catch (UnknownAmetysObjectException e)
225        {
226            _logger.debug("Can not get metadata '" + metadataName + "' on unknown content with id '" + contentId + "'", e);
227            return "";
228        }
229    }
230    /**
231     * Get the metadata of a content
232     * @param contentId The content id
233     * @param metadataName The metadata name (/ for composite)
234     * @return The name or empty if the metadata or the content does not exist
235     */
236    public static String contentMetadata(String contentId, String metadataName)
237    {
238        return contentMetadata(contentId, metadataName, null);
239    }
240    
241    private static String _getMetadata(CompositeMetadata cm, String metadataName, Locale locale)
242    {
243        int i = metadataName.indexOf("/");
244        if (i == -1)
245        {
246            if (cm.getType(metadataName).equals(MetadataType.MULTILINGUAL_STRING))
247            {
248                if (locale == null)
249                {
250                    String currentLanguage = lang();
251                    if (StringUtils.isEmpty(currentLanguage))
252                    {
253                        _logger.error("Can not get the value of a multilingual metadata " + metadataName + " without a defined locale");
254                        return "";
255                    }
256                    return cm.getLocalizedString(metadataName, new Locale(currentLanguage));
257                }
258                else
259                {
260                    return cm.getLocalizedString(metadataName, locale);
261                }
262            }
263            else
264            {
265                return cm.getString(metadataName);
266            }
267        }
268        else
269        {
270            return _getMetadata(cm.getCompositeMetadata(metadataName.substring(0, i)), metadataName.substring(i + 1), locale);
271        }
272    }
273    
274    /**
275     * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of the current content.
276     * @return an Element containing the attachments of the current content as {@link Resource}s.
277     */
278    public static Node contentAttachments()
279    {
280        Request request = ContextHelper.getRequest(_context);
281        
282        Content content = (Content) request.getAttribute(Content.class.getName());
283        
284        return contentAttachments(content);
285    }
286    
287    /**
288     * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content.
289     * @param contentId the content ID.
290     * @return an Element containing the attachments of the given content as {@link Resource}s.
291     */
292    public static Node contentAttachments(String contentId)
293    {
294        Content content = _ametysObjectResolver.resolveById(contentId);
295        
296        return contentAttachments(content);
297    }
298    
299    /**
300     * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content.
301     * @param content the content.
302     * @return an Element containing the attachments of the given content as {@link Resource}s.
303     */
304    private static Node contentAttachments(Content content)
305    {
306        if (content == null)
307        {
308            return null;
309        }
310        
311        ResourceCollection collection = content.getRootAttachments();
312        
313        return collection != null ? new ResourceCollectionElement(collection) : new EmptyElement("collection");
314    }
315    
316    /**
317     * Set the content of given id in request attribute
318     * @param contentId the id of content
319     */
320    public static void setCurrentContent(String contentId)
321    {
322        setCurrentContent(contentId, null);
323    }
324    
325    /**
326     * Set the content of given id and version in request attribute
327     * @param contentId the id of content
328     * @param versionLabel The version label
329     */
330    public static void setCurrentContent(String contentId, String versionLabel)
331    {
332        Request request = ContextHelper.getRequest(_context);
333        
334        Content content = _ametysObjectResolver.resolveById(contentId);
335        
336        if (StringUtils.isNotEmpty(versionLabel) && content instanceof VersionAwareAmetysObject)
337        {
338            String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels();
339            if (ArrayUtils.contains(allLabels, versionLabel))
340            {
341                ((VersionAwareAmetysObject) content).switchToLabel(versionLabel);
342            }
343        }
344        
345        request.setAttribute(Content.class.getName(), content);
346        
347    }
348    
349  //*************************
350    // Tag methods
351    //*************************
352    
353    /**
354     * Returns all tags of the current content.
355     * @return a list of tags.
356     */
357    public static NodeList contentTags()
358    {
359        Request request = ContextHelper.getRequest(_context);
360        
361        Content content = (Content) request.getAttribute(Content.class.getName());
362        return _contentTags(content);
363    }
364    
365    /**
366     * Returns all tags of the given content
367     * @param contentId The identifier of the content
368     * @return a list of tags.
369     */
370    public static NodeList contentTags(String contentId)
371    {
372        try
373        {
374            Content content = _ametysObjectResolver.resolveById(contentId);
375            return _contentTags(content);
376        }
377        catch (AmetysRepositoryException e)
378        {
379            _logger.warn("Cannot get tags for content '" + contentId + "'", e);
380        }
381        
382        return null;
383    }
384    
385    /**
386     * Returns all tags of the given content
387     * @param content The content
388     * @return a list of tags.
389     */
390    protected static NodeList _contentTags(Content content)
391    {
392        if (content == null)
393        {
394            return null;
395        }
396        
397        List<TagElement> list = new ArrayList<>();
398        
399        for (String tag : content.getTags())
400        {
401            list.add(new TagElement(tag));
402        }
403        
404        return new AmetysNodeList(list);
405    }
406    
407    /**
408     * Get the name of the parent of a tag.
409     * @param siteName the site name
410     * @param tagName the tag's name
411     * @return The id of parent or empty if not found
412     */
413    public static String tagParent(String siteName, String tagName)
414    {
415        Map<String, Object> contextParameters = new HashMap<>();
416        contextParameters.put("siteName", siteName);
417        
418        Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters);
419        if (tag == null)
420        {
421            return StringUtils.EMPTY;
422        }
423        
424        String parentName = tag.getParentName();
425        return parentName != null ? parentName : StringUtils.EMPTY;
426    }
427    
428    /**
429     * Get the path of a tag. The path contains the tag's parents seprated by '/'.
430     * @param siteName The site name
431     * @param tagName The unique tag's name
432     * @return The tag's path or empty string if tag does not exist
433     */
434    public static String tagPath (String siteName, String tagName)
435    {
436        Map<String, Object> contextParameters = new HashMap<>();
437        contextParameters.put("siteName", siteName);
438        
439        Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters);
440        if (tag == null)
441        {
442            return StringUtils.EMPTY;
443        }
444        
445        String path = tagName;
446        
447        Tag parentTag = tag.getParent();
448        while (parentTag != null)
449        {
450            path = parentTag.getName() + "/" + path;
451            parentTag = parentTag.getParent();
452        }
453
454        return path;
455    }
456    
457    /**
458     * Get the label of a tag
459     * @param siteName the current site
460     * @param tagName the name of the tag
461     * @param lang the lang (if i18n tag)
462     * @return the label of the tag or empty if it cannot be found
463     */
464    public static String tagLabel(String siteName, String tagName, String lang)
465    {
466        Map<String, Object> contextParameters = new HashMap<>();
467        contextParameters.put("siteName", siteName);
468        
469        Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters);
470        return tag == null ? "" : _i18nUtils.translate(tag.getTitle(), lang);
471    }
472    
473    /**
474     * Get the description of a tag
475     * @param siteName the current site
476     * @param tagName the name of the tag
477     * @param lang the lang (if i18n tag)
478     * @return the label of the tag or empty if it cannot be found
479     */
480    public static String tagDescription(String siteName, String tagName, String lang)
481    {
482        Map<String, Object> contextParameters = new HashMap<>();
483        contextParameters.put("siteName", siteName);
484        
485        Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters);
486        return tag == null ? "" : _i18nUtils.translate(tag.getDescription(), lang);
487    }
488    
489    /**
490     * Get the visibility of a tag
491     * @param siteName the current site
492     * @param tagName the name of the tag
493     * @return the lower-cased visibility of the tag ("public" or "private")
494     */
495    public static String tagVisibility(String siteName, String tagName)
496    {
497        Map<String, Object> contextParameters = new HashMap<>();
498        contextParameters.put("siteName", siteName);
499        
500        CMSTag tag = _tagProviderExtPt.getTag(tagName, contextParameters);
501        return tag == null ? "" : tag.getVisibility().toString().toLowerCase();
502    }
503    
504    /* ----------------------------- */
505    /*      Content type methods     */
506    /* ----------------------------- */
507    
508    /**
509     * Returns all tags of a content type
510     * @param contentTypeId The id of the content type
511     * @return a list of tags.
512     */
513    public static NodeList contentTypeTags(String contentTypeId)
514    {
515        ArrayList<TagElement> tags = new ArrayList<>();
516        
517        try
518        {
519            ContentType cType = _cTypeExtensionPoint.getExtension(contentTypeId);
520            if (cType != null)
521            {
522                for (String tag : cType.getTags())
523                {
524                    tags.add(new TagElement(tag));
525                }
526            }
527            else
528            {
529                _logger.error("Can not get tags of unknown content type of id '" + contentTypeId + "'");
530            }
531            
532        }
533        catch (AmetysRepositoryException e)
534        {
535            _logger.error("Can not get tags of content type of id '" + contentTypeId + "'", e);
536        }
537        
538        return new AmetysNodeList(tags);
539    }
540}