001/*
002 *  Copyright 2013 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.content;
017
018import java.io.IOException;
019import java.util.Comparator;
020import java.util.HashMap;
021import java.util.Locale;
022import java.util.Map;
023import java.util.Set;
024import java.util.TreeSet;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.ProcessingException;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.generation.ServiceableGenerator;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.contenttype.ContentType;
038import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
039import org.ametys.cms.repository.Content;
040import org.ametys.cms.repository.Content.ReferencingContentsSearch;
041import org.ametys.cms.repository.DefaultContent;
042import org.ametys.cms.tag.Tag;
043import org.ametys.cms.tag.TagProviderExtensionPoint;
044import org.ametys.core.right.RightManager;
045import org.ametys.core.right.RightManager.RightResult;
046import org.ametys.plugins.repository.AmetysObject;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.version.VersionableAmetysObject;
049import org.ametys.runtime.i18n.I18nizableText;
050
051/**
052 * Generates addition information on content
053 */
054public class AdditionalContentPropertiesGenerator extends ServiceableGenerator
055{
056    /** The AmetysObject resolver. */
057    protected AmetysObjectResolver _resolver;
058    /** the Ametys rights manager */
059    protected RightManager _rightManager;
060    /** the tag provider extension point */
061    protected TagProviderExtensionPoint _tagProviderEP;
062    private ContentTypeExtensionPoint _contentTypeEP;
063    
064    @Override
065    public void service(ServiceManager serviceManager) throws ServiceException
066    {
067        super.service(serviceManager);
068        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
069        _contentTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
070        _tagProviderEP = (TagProviderExtensionPoint) serviceManager.lookup(TagProviderExtensionPoint.ROLE);
071        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
072    }
073    
074    @Override
075    public void generate() throws IOException, SAXException, ProcessingException
076    {
077        Request request = ObjectModelHelper.getRequest(objectModel);
078        
079        String contentId = parameters.getParameter("contentId", request.getParameter("contentId"));
080        
081        Content content = null;
082        if (StringUtils.isNotEmpty(contentId))
083        {
084            content = _resolver.resolveById(contentId);
085        }
086        else
087        {
088            content = (Content) request.getAttribute(Content.class.getName());
089        }
090        
091        contentHandler.startDocument();
092        
093        AttributesImpl attrs = new AttributesImpl();
094        attrs.addCDATAAttribute("id", content.getId());
095        
096        Boolean live = content.getValue("hasLive");
097        if (live)
098        {
099            attrs.addCDATAAttribute("has-live", "true");
100        }
101        
102        if (content instanceof DefaultContent)
103        {
104            try
105            {
106                DefaultContent dc = (DefaultContent) content;
107                attrs.addCDATAAttribute("path", dc.getNode().getPath());
108            }
109            catch (Exception e)
110            {
111                getLogger().error("Cannot determine JCR path for content " + content.getId(), e);
112            }
113        }
114        XMLUtils.startElement(contentHandler, "content", attrs);
115        
116        // Take the HEAD revision
117        String revision = null;
118        if (content instanceof VersionableAmetysObject)
119        {
120            revision = ((VersionableAmetysObject) content).getRevision();
121            ((VersionableAmetysObject) content).switchToRevision(null);
122        }
123        
124        _saxContentTypesHierarchy (content);
125        _saxMixinsHierarchy(content);
126        _saxReferencingContents(content);
127        _saxTags(content);
128        _saxOtherProperties(content);
129        _saxRepositoryRights();
130        
131        if (content instanceof VersionableAmetysObject && revision != null)
132        {
133            ((VersionableAmetysObject) content).switchToRevision(revision);
134        }
135        
136        XMLUtils.endElement(contentHandler, "content");
137        contentHandler.endDocument();
138    }
139    
140    private void _saxRepositoryRights() throws SAXException
141    {
142        XMLUtils.startElement(contentHandler, "repository");
143        XMLUtils.startElement(contentHandler, "rights");
144        XMLUtils.createElement(contentHandler, "access", Boolean.toString(_rightManager.currentUserHasRight("REPOSITORY_Rights_Access", "/repository") == RightResult.RIGHT_ALLOW));
145        XMLUtils.endElement(contentHandler, "rights");
146        XMLUtils.endElement(contentHandler, "repository");
147        
148    }
149
150    /**
151     * SAX other additional properties
152     * @param content the content 
153     * @throws SAXException if an error occurred while saxing
154     */
155    protected void _saxOtherProperties (Content content) throws SAXException
156    {
157        // Nothing to do
158    }
159    
160    /**
161     * SAX content's tags
162     * @param content The content
163     * @throws SAXException if an error occurred
164     */
165    protected void _saxTags (Content content) throws SAXException
166    {
167        Set<String> tags = content.getTags();
168        
169        XMLUtils.startElement(contentHandler, "tags");
170        
171        for (String tagName : tags)
172        {
173            Tag tag = _tagProviderEP.getTag(tagName, getContextualParameters(content));
174            if (tag != null)
175            {
176                AttributesImpl attrs = new AttributesImpl();
177                attrs.addCDATAAttribute("name", tag.getName());
178                XMLUtils.startElement(contentHandler, "tag", attrs);
179                tag.getTitle().toSAX(contentHandler);
180                XMLUtils.endElement(contentHandler, "tag");
181            }
182        }
183        
184        XMLUtils.endElement(contentHandler, "tags");
185    }
186    
187    /**
188     * Get the contextual parameters for the given object
189     * @param object the object
190     * @return the object's contextual parameters
191     */
192    protected Map<String, Object> getContextualParameters (AmetysObject object)
193    {
194        return new HashMap<>();
195    }
196    
197    /**
198     * Sax the referencing contents
199     * @param content The content
200     * @throws SAXException if an error occurred while saxing
201     */
202    protected void _saxReferencingContents (Content content) throws SAXException
203    {
204        ReferencingContentsSearch referencingContents = content.searchReferencingContents(100);
205        
206        AttributesImpl referersAttrs = new AttributesImpl();
207        referersAttrs.addCDATAAttribute("count", String.valueOf(referencingContents.total()));
208        referersAttrs.addCDATAAttribute("hasOther", String.valueOf(referencingContents.hasOtherReferences()));
209        XMLUtils.startElement(contentHandler, "referencing-contents", referersAttrs);
210        
211        Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : null;
212        for (Content referrerContent : referencingContents.referencingContents())
213        {
214            AttributesImpl refererAttrs = new AttributesImpl();
215            refererAttrs.addCDATAAttribute("id", referrerContent.getId());
216            refererAttrs.addCDATAAttribute("name", referrerContent.getName());
217            refererAttrs.addCDATAAttribute("title", referrerContent.getTitle(locale));
218            
219            XMLUtils.createElement(contentHandler, "referencing-content", refererAttrs);
220        }
221        
222        XMLUtils.endElement(contentHandler, "referencing-contents");
223    }
224    
225    /**
226     * SAX the content types hierarchy
227     * @param content The content type
228     * @throws SAXException if an error occurred while saxing
229     */
230    protected void _saxContentTypesHierarchy (Content content) throws SAXException
231    {
232        // Sort content types
233        TreeSet<ContentType> treeSet = new TreeSet<>(new ContentTypeComparator());
234        for (String id : content.getTypes())
235        {
236            ContentType contentType = _contentTypeEP.getExtension(id);
237            treeSet.add(contentType);
238        }
239        
240        XMLUtils.startElement(contentHandler, "content-types");
241        for (ContentType cType : treeSet)
242        {
243            _saxContentType (cType);
244        }
245        XMLUtils.endElement(contentHandler, "content-types");
246    }
247    
248    /**
249     * SAX the content types hierarchy
250     * @param content The content type
251     * @throws SAXException if an error occurred while saxing
252     */
253    protected void _saxMixinsHierarchy (Content content) throws SAXException
254    {
255        XMLUtils.startElement(contentHandler, "mixins");
256        
257        String[] contentTypes = content.getMixinTypes();
258        for (String id : contentTypes)
259        {
260            ContentType cType = _contentTypeEP.getExtension(id);
261            _saxMixin(cType);
262        }
263        
264        XMLUtils.endElement(contentHandler, "mixins");
265    }
266    
267    /**
268     * SAX the content type hierarchy
269     * @param cType The content type
270     * @throws SAXException if an error occurred while saxing
271     */
272    protected void _saxContentType (ContentType cType) throws SAXException
273    {
274        AttributesImpl attrs = new AttributesImpl();
275        
276        attrs.addCDATAAttribute("id", cType.getId());
277        
278        if (cType.getSmallIcon() != null)
279        {
280            attrs.addCDATAAttribute("icon", cType.getSmallIcon());
281        }
282        if (cType.getIconGlyph() != null)
283        {
284            attrs.addCDATAAttribute("icon-glyph", cType.getIconGlyph());
285        }
286        if (cType.getIconDecorator() != null)
287        {
288            attrs.addCDATAAttribute("icon-decorator", cType.getIconDecorator());
289        }
290        
291        XMLUtils.startElement(contentHandler, "content-type", attrs);
292        cType.getLabel().toSAX(contentHandler, "label");
293        
294        for (String id : cType.getSupertypeIds())
295        {
296            ContentType superType = _contentTypeEP.getExtension(id);
297            _saxContentType(superType);
298        }
299        XMLUtils.endElement(contentHandler, "content-type");
300    }
301    
302    /**
303     * SAX the mixin hierarchy
304     * @param mixin The mixin type
305     * @throws SAXException if an error occurred while saxing
306     */
307    protected void _saxMixin (ContentType mixin) throws SAXException
308    {
309        AttributesImpl attrs = new AttributesImpl();
310        
311        attrs.addCDATAAttribute("id", mixin.getId());
312        if (mixin.getSmallIcon() != null)
313        {
314            attrs.addCDATAAttribute("icon", mixin.getSmallIcon());
315        }
316        if (mixin.getIconGlyph() != null)
317        {
318            attrs.addCDATAAttribute("icon-glyph", mixin.getIconGlyph());
319        }
320        if (mixin.getIconDecorator() != null)
321        {
322            attrs.addCDATAAttribute("icon-decorator", mixin.getIconDecorator());
323        }
324        
325        XMLUtils.startElement(contentHandler, "mixin", attrs);
326        mixin.getLabel().toSAX(contentHandler, "label");
327        for (String id : mixin.getSupertypeIds())
328        {
329            ContentType superType = _contentTypeEP.getExtension(id);
330            _saxMixin(superType);
331        }
332        XMLUtils.endElement(contentHandler, "mixin");
333    }
334
335    class ContentTypeComparator implements Comparator<ContentType>
336    {
337        @Override
338        public int compare(ContentType c1, ContentType c2)
339        {
340            I18nizableText t1 = c1.getLabel();
341            I18nizableText t2 = c2.getLabel();
342            
343            String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel();
344            String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel();
345            
346            int compareTo = str1.toString().compareTo(str2.toString());
347            if (compareTo == 0)
348            {
349                // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet
350                // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal 
351                return 1;
352            }
353            return compareTo;
354        }
355    }
356}