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