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.workflow;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Comparator;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import javax.jcr.RepositoryException;
031
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.configuration.Configurable;
034import org.apache.avalon.framework.configuration.Configuration;
035import org.apache.avalon.framework.configuration.ConfigurationException;
036import org.apache.avalon.framework.logger.AbstractLogEnabled;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.avalon.framework.thread.ThreadSafe;
041import org.apache.cocoon.xml.AttributesImpl;
042import org.apache.cocoon.xml.XMLUtils;
043import org.apache.commons.lang.StringUtils;
044import org.xml.sax.ContentHandler;
045import org.xml.sax.SAXException;
046
047import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
048import org.ametys.cms.contenttype.ContentTypesHelper;
049import org.ametys.cms.languages.Language;
050import org.ametys.cms.languages.LanguagesManager;
051import org.ametys.cms.repository.Content;
052import org.ametys.cms.repository.DefaultContent;
053import org.ametys.cms.repository.WorkflowAwareContent;
054import org.ametys.cms.repository.WorkflowStepExpression;
055import org.ametys.core.right.RightManager;
056import org.ametys.core.right.RightManager.RightResult;
057import org.ametys.core.user.User;
058import org.ametys.core.user.UserIdentity;
059import org.ametys.core.user.UserManager;
060import org.ametys.plugins.repository.AmetysObjectIterable;
061import org.ametys.plugins.repository.AmetysObjectResolver;
062import org.ametys.plugins.repository.AmetysRepositoryException;
063import org.ametys.plugins.repository.query.SortCriteria;
064import org.ametys.plugins.repository.query.expression.AndExpression;
065import org.ametys.plugins.repository.query.expression.Expression;
066import org.ametys.plugins.repository.query.expression.Expression.Operator;
067import org.ametys.plugins.repository.query.expression.OrExpression;
068import org.ametys.plugins.repository.query.expression.StringExpression;
069import org.ametys.plugins.workflow.support.WorkflowProvider;
070import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
071import org.ametys.runtime.i18n.I18nizableText;
072import org.ametys.runtime.parameter.ParameterHelper;
073
074import com.opensymphony.workflow.loader.StepDescriptor;
075import com.opensymphony.workflow.loader.WorkflowDescriptor;
076import com.opensymphony.workflow.spi.Step;
077
078/**
079 * Component for saxing tasks specific to an user.<br>
080 * The algorithm is the following :
081 * <ul>
082 *   <li>First, we get all granted sites for the user with the right manager.
083 *   <li>If there is at least one site allowed, we get all workflows 
084 *       associated with the granted sites.
085 *   <li>Then for each step of each task from the configuration, we get
086 *       all workflows where this step is in current steps and where
087 *       the workflow is contains in the previous list.
088 *   <li>For each workflow matching the previous conditions we
089 *       test if the user has all the rights associated with the step (from
090 *       the configuration) and then we get the content from this workflow.
091 *   <li>Finally, for each content, we sax its first page in order to access
092 *       it directly from an URL. 
093 * </ul>
094 */
095public class WorkflowTasksComponent extends AbstractLogEnabled implements Component, ThreadSafe, Configurable, Serviceable
096{
097    /** The avalon role. */
098    public static final String ROLE = WorkflowTasksComponent.class.getName();
099    
100    /** The ametys object resolver. */
101    protected AmetysObjectResolver _objectResolver;
102    
103    /** The rights manager. */
104    protected RightManager _rightManager;
105    
106    /** The users manager. */
107    protected UserManager _userManager;
108    
109    /** The content type extension point. */
110    protected ContentTypeExtensionPoint _cTypeEP;
111    
112    /** Helper for content types */
113    protected ContentTypesHelper _contentTypesHelper;
114    
115    /** The {@link WorkflowProvider} */
116    protected WorkflowProvider _workflowProvider;
117    
118    /** The languages manager */
119    protected LanguagesManager _languagesManager;
120    
121    /** The configured task map, indexed by id. */
122    protected Map<String, Task> _tasks;
123    
124    /** Allow user querying ? */
125    protected boolean _allowUserQuery;
126
127    
128    public void service(ServiceManager manager) throws ServiceException
129    {
130        _objectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
131        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
132        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
133        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
134        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
135        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
136        _languagesManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE);
137    }
138    
139    /**
140     * Configure the tasks and steps handled by this component.<br>
141     * Need the following configuration:<br>
142     * &lt;tasks&gt;<br>
143     * &#160;&#160;&lt;task label="i18n-label"&gt;<br>
144     * &#160;&#160;&#160;&#160;&lt;step id="3" rights="proposal,validation"&gt;<br>
145     * &#160;&#160;&#160;&#160;&lt;step id="4" rights="validation"&gt;<br>
146     * &#160;&#160;&#160;&#160;...<br>
147     * &#160;&#160;&lt;/task&gt;<br>
148     * &#160;&#160;...<br>
149     * &lt;/tasks&gt;<br>
150     * @param configuration The configuration as described above.
151     * @throws ConfigurationException If the configuration is invalid.
152     */
153    public void configure(Configuration configuration) throws ConfigurationException
154    {
155        Configuration tasksConf = configuration.getChild("tasks");
156        _tasks = _configureTasks(tasksConf);
157        _allowUserQuery = tasksConf.getAttributeAsBoolean("allowUserQuery", false);
158    }
159    
160    /**
161     * Allow user query ?
162     * @return true if allowed user querying, false otherwise.
163     */
164    public boolean allowUserQuery()
165    {
166        return _allowUserQuery;
167    }
168    
169    /**
170     * SAX the contents for given user
171     * @param ch the content handler to SAX into
172     * @param user the user
173     * @throws SAXException If an error occurred
174     */
175    public void toSAX(ContentHandler ch, User user) throws SAXException
176    {
177        XMLUtils.startElement(ch, "tasks");
178        
179        long start = System.currentTimeMillis();
180        
181        // Get the workflow corresponding to the configuration.
182        Map<Task, Collection<Content>> workflows = _getCorrespondingWorkflows(user);
183        
184        long wfEnd = System.currentTimeMillis();
185        getLogger().info("Contents retrieved in " + (wfEnd - start) + "ms.");
186        
187        for (Task task : workflows.keySet())
188        {
189            _saxTask(ch, task, workflows.get(task));
190        }
191        
192        long end = System.currentTimeMillis();
193        
194        // Display performance indicators.
195        AttributesImpl attrs = new AttributesImpl();
196        
197        attrs.addCDATAAttribute("Total", Long.toString(end - start));
198        attrs.addCDATAAttribute("WF", Long.toString(wfEnd - start));
199        attrs.addCDATAAttribute("SAX", Long.toString(end - wfEnd));
200        
201        XMLUtils.createElement(ch, "render", attrs);
202        
203        XMLUtils.endElement(ch, "tasks");
204    }
205    
206    /**
207     * SAX the contents for given user and task
208     * @param ch the content handler to SAX into
209     * @param user the user
210     * @param taskId the task id
211     * @throws SAXException If an error occurred
212     */
213    public void toSAX(ContentHandler ch, User user, String taskId) throws SAXException
214    {
215        Task task = _tasks.get(taskId);
216        
217        Collection<Content> getTaskContents = _getTaskContents (user, task);
218        _saxTask(ch, task, getTaskContents);
219    }
220    
221    /**
222     * Get the list of tasks managed by this component.
223     * @return the task list.
224     */
225    public Map<String, Task> getTasks()
226    {
227        return Collections.unmodifiableMap(_tasks);
228    }
229    
230    /**
231     * Get the list of contents for a given user and task.
232     * @param user the user.
233     * @param taskId the task ID.
234     * @param limit the maximum number of results, 0 for all.
235     * @return the contents as an iterable collection of contents.
236     * @throws AmetysRepositoryException If an error occurred
237     */
238    public Collection<Content> getContents(User user, String taskId, int limit) throws AmetysRepositoryException
239    {
240        if (_tasks.containsKey(taskId))
241        {
242            Task task = _tasks.get(taskId);
243            
244            return _getTaskContents(user, task, limit);
245        }
246        else
247        {
248            return Collections.emptyList();
249        }
250    }
251    
252    /**
253     * SAX a task
254     * @param ch the content handler to SAX into
255     * @param task the task to SAX
256     * @param contents the contents
257     * @throws SAXException if an error occurred while SAXing
258     */
259    protected void _saxTask (ContentHandler ch, Task task, Collection<Content> contents) throws SAXException
260    {
261        AttributesImpl taskAttr = new AttributesImpl();
262        taskAttr.addCDATAAttribute("id", task.getId());
263        _saxAdditionalAttributes (task, taskAttr);
264        
265        XMLUtils.startElement(ch, "task", taskAttr);
266        
267        task.getLabel().toSAX(ch, "label");
268        
269        for (Content content : contents)
270        {
271            // Generate the first page on which the user has all the rights.
272            try
273            {
274                _saxContent(ch, content);
275            }
276            catch (RepositoryException e)
277            {
278                throw new SAXException("Error trying to generate a content.", e);
279            }
280        }
281        
282        XMLUtils.endElement(ch, "task");
283    }
284    
285    /**
286     * SAX additional attributes
287     * @param task the task
288     * @param attrs the attributes
289     * @throws SAXException If an error occurred
290     */
291    protected void _saxAdditionalAttributes (Task task, AttributesImpl attrs) throws SAXException
292    {
293        // Nothing
294    }
295    
296    /**
297     * SAX a content.
298     * @param ch the content handler.
299     * @param content the content to sax.
300     * @throws SAXException If an error occurred
301     * @throws RepositoryException If an error occurred
302     */
303    protected void _saxContent(ContentHandler ch, Content content) throws SAXException, RepositoryException
304    {
305        UserIdentity userIdentity = content.getLastContributor();
306        User authorUser = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
307        
308        AttributesImpl attrs = new AttributesImpl();
309        attrs.addCDATAAttribute("id", content.getId());
310        attrs.addCDATAAttribute("name", content.getName());
311        attrs.addCDATAAttribute("title", content.getTitle());
312        attrs.addCDATAAttribute("lastModified", ParameterHelper.valueToString(content.getLastModified()));
313        attrs.addCDATAAttribute("contentType", StringUtils.join(content.getTypes(), ','));
314        attrs.addCDATAAttribute("language", content.getLanguage());
315       
316        Language language = _languagesManager.getAvailableLanguages().get(content.getLanguage());
317        if (language != null)
318        {
319            attrs.addCDATAAttribute("language-image", language.getSmallIcon());
320        }
321        
322        if (authorUser != null)
323        {
324            attrs.addCDATAAttribute("author", authorUser.getFullName());
325        }
326        
327        _saxAdditionalAttributes (content, attrs);
328        
329        String iconGlyph = _contentTypesHelper.getIconGlyph(content);
330        if (iconGlyph != null)
331        {
332            attrs.addCDATAAttribute("iconGlyph", iconGlyph);
333        }
334        String iconDecorator = _contentTypesHelper.getIconDecorator(content);
335        if (iconDecorator != null)
336        {
337            attrs.addCDATAAttribute("iconDecorator", iconDecorator);
338        }
339        attrs.addCDATAAttribute("smallIcon", _contentTypesHelper.getSmallIcon(content));
340        attrs.addCDATAAttribute("mediumIcon", _contentTypesHelper.getMediumIcon(content));
341        attrs.addCDATAAttribute("largeIcon", _contentTypesHelper.getLargeIcon(content));
342        
343        XMLUtils.startElement(ch, "content", attrs);
344        if (content instanceof WorkflowAwareContent)
345        {
346            _saxContentCurrentState(ch, (WorkflowAwareContent) content);
347        }
348        XMLUtils.endElement(ch, "content");
349    }
350    
351    /**
352     * SAX additional attributes
353     * @param content the content
354     * @param attrs the attributes
355     * @throws SAXException If an error occurred
356     */
357    protected void _saxAdditionalAttributes (Content content, AttributesImpl attrs) throws SAXException
358    {
359        // Nothing
360    }
361    
362    /**
363     * Get the workflows for a user.
364     * @param user the user.
365     * @return the list of contents for each task.
366     */
367    protected Map<Task, Collection<Content>> _getCorrespondingWorkflows(User user)
368    {
369        Map<Task, Collection<Content>> workflows = new LinkedHashMap<>();
370        
371        for (Task task : _tasks.values())
372        {
373            workflows.put(task, _getTaskContents(user, task));
374        }
375        
376        return workflows;
377    }
378
379    /**
380     * Get a task's contents.
381     * @param user the user. 
382     * @param task the task.
383     * @return the content collection.
384     */
385    protected Collection<Content> _getTaskContents(User user, Task task)
386    {
387        List<Content> contents = new ArrayList<>();
388        
389        int length = task.getLength();
390        int count = 0;
391        
392        for (TaskStep step : task.getSteps())
393        {
394            if (length != 0 && count == length)
395            {
396                break;
397            }
398            
399            Set<String> rights = step.getRights();
400            
401            for (Content content : _getContents(step, user))
402            {
403                if (_testContent(content, user.getIdentity(), rights))
404                {
405                    if (length != 0 && count == length)
406                    {
407                        break;
408                    }
409                    
410                    contents.add(content);
411                    count++;
412                }
413            }
414        }
415        
416        Collections.sort(contents, new LastModifiedDateComparator());
417        
418        return contents;
419    }
420    
421    /**
422     * Get a task's contents.
423     * @param user the user. 
424     * @param task the task.
425     * @param limit the maximum number of contents (0 to return all).
426     * @return the content collection.
427     */
428    protected Collection<Content> _getTaskContents(User user, Task task, int limit)
429    {
430        List<Content> contents = new ArrayList<>();
431        for (TaskStep step : task.getSteps())
432        {
433            Set<String> rights = step.getRights();
434            
435            Iterator<Content> contentIt = _getContents(step, user).iterator();
436            int stepContents = 0;
437            while (contentIt.hasNext() && (limit < 1 || stepContents < limit))
438            {
439                Content content = contentIt.next();
440                if (_testContent(content, user.getIdentity(), rights) && !contents.contains(content))
441                {
442                    contents.add(content);
443                    stepContents++;
444                }
445            }
446        }
447        
448        Collections.sort(contents, new LastModifiedDateComparator());
449        
450        if (limit > 0 && contents.size() > limit)
451        {
452            contents = contents.subList(0, limit);
453        }
454        
455        return contents;
456    }
457    
458    /**
459     * Get the contents for a step.
460     * @param step the step.
461     * @param user the user.
462     * @return an iterable collection of contents.
463     */
464    protected AmetysObjectIterable<Content> _getContents(TaskStep step, User user)
465    {
466        Expression expr = null;
467        Set<Integer> stepIds = step.getStepIds();
468        for (Integer stepId : stepIds)
469        {
470            Expression stepExpr = new WorkflowStepExpression(Operator.EQ, stepId);
471            expr = expr == null ? stepExpr : new OrExpression(expr, stepExpr);
472        }
473        
474        Expression contentExpr = null;
475        if (step.getUserContentsOnly())
476        {
477            contentExpr = new StringExpression(DefaultContent.METADATA_CONTRIBUTOR, Operator.EQ, user.getIdentity().getLogin());
478            expr = new AndExpression(expr, contentExpr);
479        }
480        
481        SortCriteria sort = new SortCriteria();
482        sort.addCriterion(DefaultContent.METADATA_MODIFIED, false, false);
483        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, sort);
484
485        return _objectResolver.query(xpathQuery);
486    }
487    
488    /**
489     * Test if the content is valid.
490     * @param content the content to test.
491     * @param user the user.
492     * @param rights the rights to test.
493     * @return true/false.
494     */
495    protected boolean _testContent(Content content, UserIdentity user, Set<String> rights)
496    {
497        // Supposer que nous avons tous les droits
498        boolean hasAllRights = true;
499        
500        Iterator<String> itRights = rights.iterator();
501        
502        // VĂ©rifier que nous avons tous les droits
503        while (itRights.hasNext() && hasAllRights)
504        {
505            String right = itRights.next();
506            
507            hasAllRights &= _rightManager.hasRight(user, right, content) == RightResult.RIGHT_ALLOW;
508        }
509        
510        return hasAllRights;
511    }   
512    
513    /**
514     * Configure the tasks.
515     * @param tasksConf the tasks root configuration.
516     * @return the task list.
517     * @throws ConfigurationException if a configuration error occurs.
518     */
519    protected Map<String, Task> _configureTasks(Configuration tasksConf) throws ConfigurationException
520    {
521        Map<String, Task> tasks = new LinkedHashMap<>();
522        for (Configuration taskConf : tasksConf.getChildren("task"))
523        {
524            Task task = new Task();
525            
526            String taskId = taskConf.getAttribute("id", "").trim();
527            
528            task.setId(taskId);
529            task.setLabel(new I18nizableText(null, taskConf.getAttribute("label", "").trim()));
530            
531            int length = taskConf.getAttributeAsInteger("length", 0);
532            task.setLength(length);
533            
534            List<TaskStep> steps = new ArrayList<>();
535            for (Configuration stepConf : taskConf.getChildren("step"))
536            {
537                TaskStep step = new TaskStep();
538                
539                String[] rightArray = StringUtils.split(stepConf.getAttribute("rights", ""), ", ");
540                step.setStepIds(_configureStepIds(stepConf));
541                step.setUserContents(stepConf.getAttributeAsBoolean("userContents", false));
542                step.setRights(new HashSet<>(Arrays.asList(rightArray)));
543                
544                steps.add(step);
545            }
546            
547            task.setSteps(steps);
548            
549            tasks.put(taskId, task);
550        }
551        return tasks;
552    }
553
554    /**
555     * Configure the step IDs.
556     * @param stepConf the step configuration.
557     * @return the step IDs as a Set of Integer.
558     * @throws ConfigurationException If an error occurred
559     */
560    protected Set<Integer> _configureStepIds(Configuration stepConf) throws ConfigurationException
561    {
562        String[] stepIdArray = StringUtils.split(stepConf.getAttribute("id", ""), ", ");
563        Set<Integer> stepIds = new HashSet<>(stepIdArray.length);
564        for (String stepId : stepIdArray)
565        {
566            try
567            {
568                stepIds.add(new Integer(stepId));
569            }
570            catch (NumberFormatException e)
571            {
572                // Ignore.
573            }
574        }
575        return stepIds;
576    }
577    
578    /**
579     * SAX workflow current step of a {@link Content}
580     * @param ch the content handler.
581     * @param content The content
582     * @throws SAXException if an error occurs while SAXing
583     * @throws RepositoryException if an error occurs while retrieving current step
584     */
585    protected void _saxContentCurrentState(ContentHandler ch, WorkflowAwareContent content) throws SAXException, RepositoryException
586    {
587        int currentStepId = 0;
588        
589        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
590        long workflowId = content.getWorkflowId();
591        
592        List<Step> steps = workflow.getCurrentSteps(workflowId);
593        if (!steps.isEmpty())
594        {
595            currentStepId = steps.get(steps.size() - 1).getStepId();
596        }
597        
598        String workflowName = workflow.getWorkflowName(workflowId);
599        WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
600        StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStepId);
601        
602        I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
603        
604        AttributesImpl attr = new AttributesImpl();
605        attr.addCDATAAttribute("id", Integer.toString(currentStepId));
606        
607        XMLUtils.startElement(ch, "workflow-step", attr);
608        workflowStepName.toSAX(ch);
609        XMLUtils.endElement(ch, "workflow-step");
610        
611        String[] icons = new String[]{"-small", "-medium", "-large"};
612        for (String icon : icons)
613        {
614            if ("application".equals(workflowStepName.getCatalogue()))
615            {
616                XMLUtils.createElement(ch, "workflow-icon" + icon, "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + icon + ".png");
617            }
618            else
619            {
620                String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
621                XMLUtils.createElement(ch, "workflow-icon" + icon, "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + icon + ".png");
622            }
623        }
624    }
625    
626    /**
627     * Class representing a task.
628     */
629    public class Task
630    {
631        /** The task ID. */
632        protected String _id;
633        
634        /** The task label. */
635        protected I18nizableText _label;
636        
637        /** The task step configuration. */
638        protected Collection<TaskStep> _steps;
639        
640        /** The max number of result */
641        protected int _length;
642        
643        /**
644         * Class representing a task.
645         */
646        public Task()
647        {
648            this(new I18nizableText(""), new ArrayList<TaskStep>());
649        }
650        
651        /**
652         * Class representing a task.
653         * @param label The label of the task
654         * @param steps  The steps of the task
655         */
656        public Task(I18nizableText label, Collection<TaskStep> steps)
657        {
658            _label = label;
659            _steps = new ArrayList<>(steps);
660        }
661
662        /**
663         * Get the id.
664         * @return the id
665         */
666        public String getId()
667        {
668            return _id;
669        }
670
671        /**
672         * Set the id.
673         * @param id the id to set
674         */
675        public void setId(String id)
676        {
677            this._id = id;
678        }
679        
680        /**
681         * Get the length.
682         * @return the length
683         */
684        public int getLength()
685        {
686            return _length;
687        }
688
689        /**
690         * Set the length.
691         * @param length the length to set
692         */
693        public void setLength(int length)
694        {
695            this._length = length;
696        }
697
698        /**
699         * Get the label.
700         * @return the label
701         */
702        public I18nizableText getLabel()
703        {
704            return _label;
705        }
706        
707        /**
708         * Set the label.
709         * @param label the label to set
710         */
711        public void setLabel(I18nizableText label)
712        {
713            this._label = label;
714        }
715        
716        /**
717         * Get the steps.
718         * @return the steps
719         */
720        public Collection<TaskStep> getSteps()
721        {
722            return _steps;
723        }
724
725        /**
726         * Set the steps.
727         * @param steps the steps to set
728         */
729        public void setSteps(Collection<TaskStep> steps)
730        {
731            this._steps = steps;
732        }
733    }
734    
735    /**
736     * Class representing a task step.
737     */
738    public class TaskStep
739    {
740        
741        /** The step ID. */
742        protected Set<Integer> _stepIds;
743        
744        /** Only return user contents. */
745        protected boolean _userContents;
746        
747        /** The rights to have. */
748        protected Set<String> _rights;
749        
750        /**
751         * Construct a TaskStep object.
752         */
753        public TaskStep()
754        {
755            this(new HashSet<Integer>(), false, new HashSet<String>());
756        }
757
758        /**
759         * Construct a TaskStep object with parameters.
760         * @param stepIds the step ID.
761         * @param userContents the user contents.
762         * @param rights the rights.
763         */
764        public TaskStep(Set<Integer> stepIds, boolean userContents, Set<String> rights)
765        {
766            super();
767            _stepIds = stepIds;
768            _userContents = userContents;
769            _rights = new HashSet<>(rights);
770        }
771
772        /**
773         * Get the stepId.
774         * @return the stepId
775         */
776        public Set<Integer> getStepIds()
777        {
778            return _stepIds;
779        }
780
781        /**
782         * Set the stepId.
783         * @param stepIds the stepId to set
784         */
785        public void setStepIds(Set<Integer> stepIds)
786        {
787            this._stepIds = stepIds;
788        }
789
790        /**
791         * Get the userContents.
792         * @return the userContents
793         */
794        public boolean getUserContentsOnly()
795        {
796            return _userContents;
797        }
798
799        /**
800         * Set the userContents.
801         * @param userContents the userContents to set
802         */
803        public void setUserContents(boolean userContents)
804        {
805            this._userContents = userContents;
806        }
807
808        /**
809         * Get the rights.
810         * @return the rights
811         */
812        public Set<String> getRights()
813        {
814            return _rights;
815        }
816
817        /**
818         * Set the rights.
819         * @param rights the rights to set
820         */
821        public void setRights(Set<String> rights)
822        {
823            this._rights = rights;
824        }
825        
826    }
827    
828    /**
829     * Compares two contents on the last modified date.
830     */
831    public class LastModifiedDateComparator implements Comparator<Content>
832    {
833        @Override
834        public int compare(Content c1, Content c2)
835        {
836            // The content is before if 
837            return c2.getLastModified().compareTo(c1.getLastModified());
838        }
839    }
840}