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.content;
017
018import java.io.IOException;
019import java.text.DateFormat;
020import java.text.SimpleDateFormat;
021import java.time.LocalDate;
022import java.time.format.DateTimeFormatter;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.Date;
026import java.util.List;
027import java.util.Locale;
028
029import javax.jcr.RepositoryException;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.ProcessingException;
034import org.apache.cocoon.environment.ObjectModelHelper;
035import org.apache.cocoon.environment.Request;
036import org.apache.cocoon.generation.Generator;
037import org.apache.cocoon.generation.ServiceableGenerator;
038import org.apache.cocoon.i18n.I18nUtils;
039import org.apache.cocoon.xml.AttributesImpl;
040import org.apache.cocoon.xml.XMLUtils;
041import org.apache.commons.lang.ArrayUtils;
042import org.apache.commons.lang.StringUtils;
043import org.xml.sax.SAXException;
044
045import org.ametys.cms.contenttype.ContentType;
046import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
047import org.ametys.cms.contenttype.ContentTypesHelper;
048import org.ametys.cms.contenttype.MetadataManager;
049import org.ametys.cms.contenttype.MetadataSet;
050import org.ametys.cms.languages.Language;
051import org.ametys.cms.languages.LanguagesManager;
052import org.ametys.cms.repository.Content;
053import org.ametys.cms.repository.ReactionableObject;
054import org.ametys.cms.repository.ReactionableObject.ReactionType;
055import org.ametys.cms.repository.WorkflowAwareContent;
056import org.ametys.cms.repository.comment.Comment;
057import org.ametys.cms.repository.comment.CommentableContent;
058import org.ametys.core.user.CurrentUserProvider;
059import org.ametys.core.user.UserIdentity;
060import org.ametys.core.util.DateUtils;
061import org.ametys.plugins.core.user.UserHelper;
062import org.ametys.plugins.repository.AmetysRepositoryException;
063import org.ametys.plugins.repository.dublincore.DublinCoreAwareAmetysObject;
064import org.ametys.plugins.repository.jcr.JCRAmetysObject;
065import org.ametys.plugins.repository.version.VersionAwareAmetysObject;
066import org.ametys.plugins.workflow.store.AmetysStep;
067import org.ametys.plugins.workflow.support.WorkflowHelper;
068import org.ametys.plugins.workflow.support.WorkflowProvider;
069import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
070import org.ametys.runtime.i18n.I18nizableText;
071
072import com.opensymphony.workflow.WorkflowException;
073import com.opensymphony.workflow.spi.Step;
074
075/**
076 * {@link Generator} for rendering raw content data.
077 */
078public class ContentGenerator extends ServiceableGenerator
079{
080    /** The display date format. */
081    protected static final DateFormat _DC_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
082    
083    /** Content type extension point. */
084    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
085    /** Metadata manager. */
086    protected MetadataManager _metadataManager;
087    /** The language manager */
088    protected LanguagesManager _languageManager;
089    /** The workflow provider */
090    protected WorkflowProvider _workflowProvider;
091    /** The workflow helper */
092    protected WorkflowHelper _worklflowHelper;
093    /** Helper for content types */
094    protected ContentTypesHelper _cTypesHelper; 
095    /** The content helper */
096    protected ContentHelper _contentHelper;
097    /** The current user provider */
098    protected CurrentUserProvider _userProvider;
099    /** The user helper */
100    protected UserHelper _userHelper;
101    
102    @Override
103    public void service(ServiceManager serviceManager) throws ServiceException
104    {
105        super.service(serviceManager);
106        _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE);
107        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
108        _metadataManager = (MetadataManager) serviceManager.lookup(MetadataManager.ROLE);
109        _languageManager = (LanguagesManager) serviceManager.lookup(LanguagesManager.ROLE);
110        _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE);
111        _worklflowHelper = (WorkflowHelper) serviceManager.lookup(WorkflowHelper.ROLE);
112        _cTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
113        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
114        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
115    }
116    
117    public void generate() throws IOException, SAXException, ProcessingException
118    {
119        contentHandler.startDocument();
120        _generateContent();
121        contentHandler.endDocument();
122    }
123    
124    /**
125     * Generate the content (with the start/end document)
126     * @throws SAXException if an error occurs while SAXing
127     * @throws IOException if an error occurs
128     * @throws ProcessingException if an error occurs
129     */
130    protected void _generateContent() throws SAXException, IOException, ProcessingException
131    {
132        Request request = ObjectModelHelper.getRequest(objectModel);
133        Content content = (Content) request.getAttribute(Content.class.getName());
134        
135        // SAX the content
136        _saxContent(content, getDefaultLocale(request));
137    }
138    
139    /**
140     * Get the default locale to use to sax localized values
141     * @param request the request
142     * @return the default locale
143     */
144    protected Locale getDefaultLocale(Request request)
145    {
146        String lang = parameters.getParameter("lang", request.getParameter("lang"));
147        return StringUtils.isNotEmpty(lang) ? new Locale(lang) : I18nUtils.findLocale(objectModel, "locale", null, Locale.getDefault(), true);
148    }
149    
150    /**
151     * SAX the content 
152     * @param content The content to SAX
153     * @param defaultLocale The default locale to use to sax localized values if the content's language is null.
154     * @throws SAXException if an error occurs while SAXing
155     * @throws IOException if an error occurs
156     * @throws ProcessingException if an error occurs
157     */
158    protected void _saxContent (Content content, Locale defaultLocale) throws SAXException, IOException, ProcessingException
159    {
160        AttributesImpl attrs = new AttributesImpl();
161        attrs.addCDATAAttribute("id", content.getId());
162        if (content instanceof JCRAmetysObject)
163        {
164            try
165            {
166                attrs.addCDATAAttribute("uuid", ((JCRAmetysObject) content).getNode().getIdentifier());
167            }
168            catch (RepositoryException e)
169            {
170                throw new ProcessingException("Unable to get jcr UUID for content '" + content.getId() + "'", e);
171            }
172        }
173        attrs.addCDATAAttribute("id", content.getId());
174        attrs.addCDATAAttribute("name", content.getName());
175        attrs.addCDATAAttribute("title", content.getTitle(defaultLocale));
176        if (content.getLanguage() != null)
177        {
178            attrs.addCDATAAttribute("language", content.getLanguage());
179        }
180        attrs.addCDATAAttribute("createdAt", DateUtils.dateToString(content.getCreationDate()));
181        attrs.addCDATAAttribute("creator", UserIdentity.userIdentityToString(content.getCreator()));
182        attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified()));
183        Date lastValidatedAt = content.getLastValidationDate();
184        if (lastValidatedAt != null)
185        {
186            attrs.addCDATAAttribute("lastValidatedAt", DateUtils.dateToString(lastValidatedAt));
187        }
188        attrs.addCDATAAttribute("lastContributor", UserIdentity.userIdentityToString(content.getLastContributor()));
189        attrs.addCDATAAttribute("commentable", Boolean.toString(content instanceof CommentableContent));
190        
191        _addAttributeIfNotNull (attrs, "iconGlyph", _cTypesHelper.getIconGlyph(content));
192        _addAttributeIfNotNull (attrs, "iconDecorator", _cTypesHelper.getIconDecorator(content));
193        
194        _addAttributeIfNotNull (attrs, "smallIcon", _cTypesHelper.getSmallIcon(content));
195        _addAttributeIfNotNull (attrs, "mediumIcon", _cTypesHelper.getMediumIcon(content));
196        _addAttributeIfNotNull (attrs, "largeIcon", _cTypesHelper.getLargeIcon(content));
197        
198        XMLUtils.startElement(contentHandler, "content", attrs);
199
200        MetadataSet metadataSet = _getMetadataSet(content);
201        XMLUtils.startElement(contentHandler, "metadata");
202        _saxMetadata(content, metadataSet, defaultLocale);
203        XMLUtils.endElement(contentHandler, "metadata");
204        
205        if (metadataSet.isEdition())
206        {
207            XMLUtils.startElement(contentHandler, "comments");
208            _saxMetadataComments(content, metadataSet, defaultLocale);
209            XMLUtils.endElement(contentHandler, "comments");
210        }
211        
212        String[] cTypes = (String[]) ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
213        for (String cTypeId : cTypes)
214        {
215            ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId);
216            cType.saxContentTypeAdditionalData(contentHandler, content);
217        }
218        
219        // FIXME CMS-3057
220        Request request = ObjectModelHelper.getRequest(objectModel);
221        boolean displayWorkflow = !"true".equals(request.getParameter("ignore-workflow"));
222        
223        if (displayWorkflow)
224        {
225            _saxWorkflowStep(content);
226        }
227        
228        _saxLanguage (content);
229        
230        _saxDublinCoreMetadata(content);
231        
232        _saxContentReactions(content);
233
234        _saxContentComments(content);
235
236        _saxOtherData(content, defaultLocale);
237        
238        XMLUtils.endElement(contentHandler, "content");
239    }
240    
241    /**
242     * Add attribute if value is not null
243     * @param attrs The attributes
244     * @param name The name of attribute
245     * @param value The value
246     */
247    protected void _addAttributeIfNotNull (AttributesImpl attrs, String name, String value)
248    {
249        if (value != null)
250        {
251            attrs.addCDATAAttribute(name, value);
252        }
253    }
254    
255    /**
256     * SAX the comments of the content
257     * @param content The content to consider. Cannot be null.
258     * @throws SAXException if an error occurs while SAXing.
259     */
260    protected void _saxContentReactions(Content content) throws SAXException
261    {
262        if (content instanceof ReactionableObject)
263        {
264            XMLUtils.startElement(contentHandler, "reactions");
265            
266            List<UserIdentity> likers = ((ReactionableObject) content).getReactionUsers(ReactionType.LIKE);
267            if (!likers.isEmpty())
268            {
269                AttributesImpl attrs = new AttributesImpl();
270                attrs.addCDATAAttribute("type", ReactionType.LIKE.name());
271                XMLUtils.startElement(contentHandler, "reaction", attrs);
272                
273                for (UserIdentity liker : likers)
274                {
275                    _userHelper.saxUserIdentity(liker, contentHandler, "actor");
276                }
277                
278                XMLUtils.endElement(contentHandler, "reaction");
279            }
280            XMLUtils.endElement(contentHandler, "reactions");
281        }        
282    }
283    
284    /**
285     * SAX the comments of the content
286     * @param content The content to consider. Cannot be null.
287     * @throws SAXException if an error occurs while SAXing.
288     */
289    protected void _saxContentComments(Content content) throws SAXException
290    {
291        if (content instanceof CommentableContent)
292        {
293            CommentableContent cContent = (CommentableContent) content;
294            
295            List<Comment> comments = cContent.getComments(false, true);
296            int level = 0;
297            _saxComments(comments, "comments", level);
298        }        
299    }
300
301    /**
302     * Sax the comments
303     * @param comments the list of comments to sax
304     * @param elementName the name of the xml element
305     * @param level the level of comment (0 for parent comment, 1 for sub-comment, etc ....)
306     * @throws SAXException if an error occurs saxing
307     */
308    protected void _saxComments(List<Comment> comments, String elementName, int level) throws SAXException
309    {
310        if (comments.size() > 0)
311        {
312            XMLUtils.startElement(contentHandler, elementName);
313            
314            for (Comment comment : comments)
315            {
316                _saxComment(comment, level);
317            }
318            
319            XMLUtils.endElement(contentHandler, elementName);
320        }
321    }
322
323    /**
324     * Sax the comment
325     * @param comment the comment
326     * @param level the level of comment (0 for parent comment, 1 for sub-comment, etc ....)
327     * @throws SAXException if an error occurs saxing
328     */
329    protected void _saxComment(Comment comment, int level) throws SAXException
330    {
331        AttributesImpl attrs = new AttributesImpl();
332
333        attrs.addCDATAAttribute("id", comment.getId());
334        attrs.addCDATAAttribute("creation-date", DateUtils.dateToString(comment.getCreationDate()));
335        attrs.addCDATAAttribute("level", String.valueOf(level));
336        
337        if (!StringUtils.isBlank(comment.getAuthorName()))
338        {
339            attrs.addCDATAAttribute("author-name", comment.getAuthorName());
340        }
341        
342        if (!comment.isEmailHidden() && !StringUtils.isBlank(comment.getAuthorEmail()))
343        {
344            attrs.addCDATAAttribute("author-email", comment.getAuthorEmail());
345        }
346
347        if (!StringUtils.isBlank(comment.getAuthorURL()))
348        {
349            attrs.addCDATAAttribute("author-url", comment.getAuthorURL());
350        }
351
352        List<UserIdentity> likers = comment.getReactionUsers(ReactionType.LIKE);
353        attrs.addCDATAAttribute("nb-like", String.valueOf(likers.size()));
354        
355        XMLUtils.startElement(contentHandler, "comment", attrs);
356
357        if (comment.getContent() != null)
358        {
359            String[] contents = comment.getContent().split("\r?\n");
360            for (String c : contents)
361            {
362                XMLUtils.createElement(contentHandler, "p", c);
363            }
364        }
365        
366        XMLUtils.startElement(contentHandler, "likers");
367        for (UserIdentity liker : likers)
368        {
369            _userHelper.saxUserIdentity(liker, contentHandler, "liker");
370        }
371        XMLUtils.endElement(contentHandler, "likers");
372
373        _saxComments(comment.getSubComment(false, true), "sub-comments", level + 1);
374
375        XMLUtils.endElement(contentHandler, "comment");
376    }
377    
378    /**
379     * SAX the content language
380     * @param content The content
381     * @throws SAXException if an error occurs while SAXing.
382     */
383    protected void _saxLanguage (Content content) throws SAXException
384    {
385        String code = content.getLanguage();
386        if (code != null)
387        {
388            Language language = _languageManager.getLanguage(code);
389            
390            AttributesImpl atts = new AttributesImpl();
391            atts.addCDATAAttribute("code", code);
392            
393            if (language != null)
394            {
395                atts.addCDATAAttribute("icon-small", language.getSmallIcon());
396                atts.addCDATAAttribute("icon-medium", language.getMediumIcon());
397                atts.addCDATAAttribute("icon-large", language.getLargeIcon());
398            }
399            
400            XMLUtils.startElement(contentHandler, "content-language", atts);
401            if (language != null)
402            {
403                language.getLabel().toSAX(contentHandler);
404            }
405            XMLUtils.endElement(contentHandler, "content-language");
406        }
407    }
408
409    /**
410     * SAX the workflow step if the content is a <code>WorkflowAwareContent</code>
411     * @param content The content
412     * @throws SAXException if an error occurs while SAXing.
413     */
414    protected void _saxWorkflowStep (Content content) throws SAXException
415    {
416        if (content instanceof WorkflowAwareContent)
417        {
418            WorkflowAwareContent waContent = (WorkflowAwareContent) content;
419            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
420            
421            try
422            {
423                long workflowId = waContent.getWorkflowId();
424                String workflowName = workflow.getWorkflowName(workflowId);
425                
426                Step currentStep = _getCurrentStep(waContent, workflow);
427                
428                int currentStepId = currentStep.getStepId();
429                
430                I18nizableText workflowStepName = new I18nizableText("application",  _worklflowHelper.getStepName(workflowName, currentStepId));
431                
432                AttributesImpl atts = new AttributesImpl();
433                atts.addAttribute("", "id", "id", "CDATA", String.valueOf(currentStepId));
434                if ("application".equals(workflowStepName.getCatalogue()))
435                {
436                    atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-small.png");
437                    atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-medium.png");
438                    atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-large.png");
439                }
440                else
441                {
442                    String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
443                    atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png");
444                    atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-medium.png");
445                    atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-large.png");
446                }
447                
448                XMLUtils.startElement(contentHandler, "workflow-step", atts);
449                workflowStepName.toSAX(contentHandler);
450                XMLUtils.endElement(contentHandler, "workflow-step");
451            }
452            catch (AmetysRepositoryException e)
453            {
454                // Current step id was not positioned
455            }
456            catch (WorkflowException e)
457            {
458                // Ignore, just don't SAX the workflow step.
459            }
460        }
461    }
462    
463    /**
464     * SAX content metadata.
465     * @param content the content.
466     * @param metadataSet the metadata set.
467     * @param defaultLocale The default locale to use to sax localized values if the content's language is null.
468     * @throws SAXException if an error occurs while SAXing.
469     * @throws IOException if an error occurs.
470     * @throws ProcessingException if an error occurs.
471     */
472    protected void _saxMetadata(Content content, MetadataSet metadataSet, Locale defaultLocale) throws SAXException, ProcessingException, IOException
473    {
474        _metadataManager.saxMetadata(contentHandler, content, metadataSet, defaultLocale);
475    }
476    
477    /**
478     * SAX metadata comments.
479     * @param content the content.
480     * @param metadataSet the metadata set.
481     * @param defaultLocale The default locale to use to sax localized values if the content's language is null.
482     * @throws SAXException if an error occurs while SAXing.
483     * @throws IOException if an error occurs.
484     * @throws ProcessingException if an error occurs.
485     */
486    protected void _saxMetadataComments(Content content, MetadataSet metadataSet, Locale defaultLocale) throws SAXException, ProcessingException, IOException
487    {
488        _metadataManager.saxMetadataComments(contentHandler, content, metadataSet, defaultLocale);
489    }
490    
491    /**
492     * SAX content Dublin Core metadata.
493     * @param dcObject the Dublin Core object.
494     * @throws SAXException if an error occurs while SAXing.
495     */
496    protected void _saxDublinCoreMetadata(DublinCoreAwareAmetysObject dcObject) throws SAXException
497    {
498        XMLUtils.startElement(contentHandler, "dublin-core-metadata");
499        _saxIfNotNull("title", dcObject.getDCTitle());
500        _saxIfNotNull("creator", dcObject.getDCCreator());
501        _saxIfNotNull("subject", dcObject.getDCSubject());
502        _saxIfNotNull("description", dcObject.getDCDescription());
503        _saxIfNotNull("publisher", dcObject.getDCPublisher());
504        _saxIfNotNull("contributor", dcObject.getDCContributor());
505        _saxIfNotNull("date", dcObject.getDCDate());
506        _saxIfNotNull("type", dcObject.getDCType());
507        _saxIfNotNull("format", dcObject.getDCFormat());
508        _saxIfNotNull("identifier", dcObject.getDCIdentifier());
509        _saxIfNotNull("source", dcObject.getDCSource());
510        _saxIfNotNull("language", dcObject.getDCLanguage());
511        _saxIfNotNull("relation", dcObject.getDCRelation());
512        _saxIfNotNull("coverage", dcObject.getDCCoverage());
513        _saxIfNotNull("rights", dcObject.getDCRights());
514        XMLUtils.endElement(contentHandler, "dublin-core-metadata");
515    }
516    
517    /**
518     * SAX string Dublin Core metadata.
519     * @param name the metadata name.
520     * @param value the metadata value.
521     * @throws SAXException if an error occurs while SAXing.
522     */
523    protected void _saxIfNotNull(String name, String value) throws SAXException
524    {
525        if (value != null)
526        {
527            XMLUtils.createElement(contentHandler, name, value);
528        }
529    }
530    
531    /**
532     * SAX string Dublin Core metadata.
533     * @param name the metadata name.
534     * @param values the metadata values.
535     * @throws SAXException if an error occurs while SAXing.
536     */
537    protected void _saxIfNotNull(String name, String[] values) throws SAXException
538    {
539        if (values != null)
540        {
541            for (String value : values)
542            {
543                XMLUtils.createElement(contentHandler, name, value);
544            }
545        }
546    }
547    
548    /**
549     * SAX date Dublin Core metadata.
550     * @param name the metadata name.
551     * @param value the metadata value.
552     * @throws SAXException if an error occurs while SAXing.
553     */
554    protected void _saxIfNotNull(String name, Date value) throws SAXException
555    {
556        if (value != null)
557        {
558            LocalDate ld = DateUtils.asLocalDate(value);
559            XMLUtils.createElement(contentHandler, name, ld.format(DateTimeFormatter.ISO_LOCAL_DATE));
560        }
561    }
562
563    /**
564     * SAX any other data needed by the view.<p>
565     * Default implementation does nothing.
566     * @param content the content.
567     * @throws SAXException if an error occurs while SAXing.
568     * @throws ProcessingException if an error occurs.
569     * @deprecated Use and/or override {@link #_saxOtherData(Content, Locale)} instead
570     */
571    @Deprecated
572    protected void _saxOtherData(Content content) throws SAXException, ProcessingException
573    {
574        // No other data to SAX
575    }
576    
577    /**
578     * SAX any other data needed by the view.<p>
579     * Default implementation does nothing.
580     * @param content the content.
581     * @param defaultLocale The default locale
582     * @throws SAXException if an error occurs while SAXing.
583     * @throws ProcessingException if an error occurs.
584     * @throws IOException if an error occurs.
585     */
586    protected void _saxOtherData(Content content, Locale defaultLocale) throws SAXException, ProcessingException, IOException
587    {
588        // For legacy purposes
589        _saxOtherData(content);
590    }
591    
592    /**
593     * Retrieves the metadata set to be used when SAX'ing metadata and metadata comments.
594     * @param content The content to consider. Cannot be null.
595     * @return The retrieved metadata set
596     * @throws ProcessingException If the metadata set could not be retrieved
597     */
598    protected MetadataSet _getMetadataSet(Content content) throws ProcessingException
599    {
600        boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false);
601        String metadataSetName = parameters.getParameter("metadataSetName", "");
602        if (StringUtils.isBlank(metadataSetName))
603        {
604            metadataSetName = "main";
605        }
606        
607        MetadataSet metadataSet = null;
608        
609        if (isEditionMetadataSet)
610        {
611            metadataSet = _cTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes());
612        }
613        else
614        {
615            metadataSet = _cTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes());
616        }
617        
618        if (metadataSet == null)
619        {
620            throw new ProcessingException(String.format("Unknown metadata set '%s' of type '%s' for content type(s) '%s'",
621                                          metadataSetName, isEditionMetadataSet ? "edition" : "view", StringUtils.join(content.getTypes(), ','))); 
622        }
623        
624        return metadataSet;
625    }
626    
627    /**
628     * Get a content's step, wherever it works on the base version or not.
629     * @param content the content.
630     * @param workflow The workflow impl to use 
631     * @return the content's workflow step.
632     * @throws WorkflowException if an error occurs.
633     */
634    protected Step _getCurrentStep(WorkflowAwareContent content, AmetysObjectWorkflow workflow) throws WorkflowException
635    {
636        long workflowId = content.getWorkflowId();
637        
638        Step currentStep = (Step) workflow.getCurrentSteps(workflowId).get(0);
639        
640        if (content instanceof VersionAwareAmetysObject)
641        {
642            VersionAwareAmetysObject vaContent = (VersionAwareAmetysObject) content;
643            String currentRevision = vaContent.getRevision();
644            
645            if (currentRevision != null)
646            {
647                
648                String[] allRevisions = vaContent.getAllRevisions();
649                int currentRevIndex = ArrayUtils.indexOf(allRevisions, currentRevision);
650                
651                if (currentRevIndex > -1 && currentRevIndex < (allRevisions.length - 1))
652                {
653                    String nextRevision = allRevisions[currentRevIndex + 1];
654                    
655                    Date currentRevTimestamp = vaContent.getRevisionTimestamp();
656                    Date nextRevTimestamp = vaContent.getRevisionTimestamp(nextRevision);
657                    
658                    // Get all steps between the two revisions. 
659                    List<Step> steps = _worklflowHelper.getStepsBetween(workflow, workflowId, currentRevTimestamp, nextRevTimestamp);
660                    
661                    // In the old workflow structure
662                    // We take the second, which is current revision's last step.
663                    if (steps.size() > 0 && steps.get(0) instanceof AmetysStep)
664                    {
665                        AmetysStep amStep = (AmetysStep) steps.get(0);
666                        if (amStep.getProperty("actionFinishDate") != null)
667                        {
668                            // New workflow structure detected: cut the first workflow step
669                            // in the list, as it belongs to the next version.
670                            steps = steps.subList(1, steps.size());
671                        }
672                    }
673                    
674                    // Order by step descendant.
675                    Collections.sort(steps, new Comparator<Step>()
676                    {
677                        public int compare(Step step1, Step step2)
678                        {
679                            return -new Long(step1.getId()).compareTo(step2.getId());
680                        }
681                    });
682                    
683                    // The first step in the list is the current version's last workflow step.
684                    if (steps.size() > 0)
685                    {
686                        currentStep = steps.get(0);
687                    }
688                }
689            }
690        }
691        return currentStep;
692    }
693    
694}