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