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.core.util.DateUtils;
062import org.ametys.plugins.repository.AmetysObjectIterable;
063import org.ametys.plugins.repository.AmetysObjectResolver;
064import org.ametys.plugins.repository.AmetysRepositoryException;
065import org.ametys.plugins.repository.query.SortCriteria;
066import org.ametys.plugins.repository.query.expression.AndExpression;
067import org.ametys.plugins.repository.query.expression.Expression;
068import org.ametys.plugins.repository.query.expression.Expression.Operator;
069import org.ametys.plugins.repository.query.expression.OrExpression;
070import org.ametys.plugins.repository.query.expression.UserExpression;
071import org.ametys.plugins.workflow.support.WorkflowProvider;
072import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
073import org.ametys.runtime.i18n.I18nizableText;
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, task);
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     * @param task the task
306     * @throws SAXException If an error occurred
307     * @throws RepositoryException If an error occurred
308     */
309    protected void _saxContent(ContentHandler ch, Content content, Task task) throws SAXException, RepositoryException
310    {
311        UserIdentity userIdentity = content.getLastContributor();
312        User authorUser = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
313        
314        AttributesImpl attrs = new AttributesImpl();
315        attrs.addCDATAAttribute("id", content.getId());
316        attrs.addCDATAAttribute("name", content.getName());
317        attrs.addCDATAAttribute("title", _contentHelper.getTitle(content));
318        attrs.addCDATAAttribute("lastModified", DateUtils.zonedDateTimeToString(content.getLastModified()));
319        attrs.addCDATAAttribute("contentType", StringUtils.join(content.getTypes(), ','));
320        if (content.getLanguage() != null)
321        {
322            attrs.addCDATAAttribute("language", content.getLanguage());
323            Language language = _languagesManager.getAvailableLanguages().get(content.getLanguage());
324            if (language != null)
325            {
326                attrs.addCDATAAttribute("language-image", language.getSmallIcon());
327            }
328        }
329       
330        if (authorUser != null)
331        {
332            attrs.addCDATAAttribute("author", authorUser.getFullName());
333        }
334        
335        _saxAdditionalAttributes (content, task, attrs);
336        
337        String iconGlyph = _contentTypesHelper.getIconGlyph(content);
338        if (iconGlyph != null)
339        {
340            attrs.addCDATAAttribute("iconGlyph", iconGlyph);
341        }
342        String iconDecorator = _contentTypesHelper.getIconDecorator(content);
343        if (iconDecorator != null)
344        {
345            attrs.addCDATAAttribute("iconDecorator", iconDecorator);
346        }
347        attrs.addCDATAAttribute("smallIcon", _contentTypesHelper.getSmallIcon(content));
348        attrs.addCDATAAttribute("mediumIcon", _contentTypesHelper.getMediumIcon(content));
349        attrs.addCDATAAttribute("largeIcon", _contentTypesHelper.getLargeIcon(content));
350        
351        XMLUtils.startElement(ch, "content", attrs);
352        if (content instanceof WorkflowAwareContent)
353        {
354            _saxContentCurrentState(ch, (WorkflowAwareContent) content);
355        }
356        _saxAdditionalData(ch, content, task);
357        XMLUtils.endElement(ch, "content");
358    }
359    
360    /**
361     * SAX additional attributes
362     * @param content the content
363     * @param task the task
364     * @param attrs the attributes
365     * @throws SAXException If an error occurred
366     */
367    protected void _saxAdditionalAttributes (Content content, Task task, AttributesImpl attrs) throws SAXException
368    {
369        // Nothing
370    }
371    
372    /**
373     * SAX additional data
374     * @param ch the content handler.
375     * @param content the content
376     * @param task the task
377     * @throws SAXException If an error occurred
378     */
379    protected void _saxAdditionalData(ContentHandler ch, Content content, Task task) throws SAXException
380    {
381        // Nothing
382    }
383    
384    /**
385     * Get the workflows for a user.
386     * @param user the user.
387     * @return the list of contents for each task.
388     */
389    protected Map<Task, Collection<Content>> _getCorrespondingWorkflows(User user)
390    {
391        Map<Task, Collection<Content>> workflows = new LinkedHashMap<>();
392        
393        for (Task task : _tasks.values())
394        {
395            workflows.put(task, _getTaskContents(user, task));
396        }
397        
398        return workflows;
399    }
400
401    /**
402     * Get a task's contents.
403     * @param user the user. 
404     * @param task the task.
405     * @return the content collection.
406     */
407    protected Collection<Content> _getTaskContents(User user, Task task)
408    {
409        List<Content> contents = new ArrayList<>();
410        
411        int length = task.getLength();
412        int count = 0;
413        
414        for (TaskStep step : task.getSteps())
415        {
416            if (length != 0 && count == length)
417            {
418                break;
419            }
420            
421            Set<String> rights = step.getRights();
422            
423            for (Content content : _getContents(step, user))
424            {
425                if (_testContent(content, user.getIdentity(), rights))
426                {
427                    if (length != 0 && count == length)
428                    {
429                        break;
430                    }
431                    
432                    contents.add(content);
433                    count++;
434                }
435            }
436        }
437        
438        Collections.sort(contents, new LastModifiedDateComparator());
439        
440        return contents;
441    }
442    
443    /**
444     * Get a task's contents.
445     * @param user the user. 
446     * @param task the task.
447     * @param limit the maximum number of contents (0 to return all).
448     * @return the content collection.
449     */
450    protected Collection<Content> _getTaskContents(User user, Task task, int limit)
451    {
452        List<Content> contents = new ArrayList<>();
453        for (TaskStep step : task.getSteps())
454        {
455            Set<String> rights = step.getRights();
456            
457            Iterator<Content> contentIt = _getContents(step, user).iterator();
458            int stepContents = 0;
459            while (contentIt.hasNext() && (limit < 1 || stepContents < limit))
460            {
461                Content content = contentIt.next();
462                if (_testContent(content, user.getIdentity(), rights) && !contents.contains(content))
463                {
464                    contents.add(content);
465                    stepContents++;
466                }
467            }
468        }
469        
470        Collections.sort(contents, new LastModifiedDateComparator());
471        
472        if (limit > 0 && contents.size() > limit)
473        {
474            contents = contents.subList(0, limit);
475        }
476        
477        return contents;
478    }
479    
480    /**
481     * Get the contents for a step.
482     * @param step the step.
483     * @param user the user.
484     * @return an iterable collection of contents.
485     */
486    protected AmetysObjectIterable<Content> _getContents(TaskStep step, User user)
487    {
488        List<Expression> exprs = _getContentsAndExpressions(step, user);
489        Expression contentExpr = new AndExpression(exprs.toArray(new Expression[exprs.size()]));
490        
491        SortCriteria sort = new SortCriteria();
492        sort.addCriterion(DefaultContent.METADATA_MODIFIED, false, false);
493        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", contentExpr, sort);
494
495        return _objectResolver.query(xpathQuery);
496    }
497    
498    /**
499     * Get the AND expressions to request on contents
500     * @param step the step.
501     * @param user the user.
502     * @return The content's expressions
503     */
504    protected List<Expression> _getContentsAndExpressions(TaskStep step, User user)
505    {
506        List<Expression> exprs = new ArrayList<>();
507        
508        List<Expression> stepExprs = new ArrayList<>();
509        for (Integer stepId : step.getStepIds())
510        {
511            stepExprs.add(new WorkflowStepExpression(Operator.EQ, stepId));
512        }
513        if (stepExprs.size() > 0)
514        {
515            exprs.add(new OrExpression(stepExprs.toArray(new Expression[stepExprs.size()])));
516        }
517        
518        if (step.getUserContentsOnly())
519        {
520            exprs.add(new UserExpression(DefaultContent.METADATA_CONTRIBUTOR, Operator.EQ, user.getIdentity()));
521        }
522        
523        return exprs;
524    }
525    
526    /**
527     * Test if the content is valid.
528     * @param content the content to test.
529     * @param user the user.
530     * @param rights the rights to test.
531     * @return true/false.
532     */
533    protected boolean _testContent(Content content, UserIdentity user, Set<String> rights)
534    {
535        // Supposer que nous avons tous les droits
536        boolean hasAllRights = true;
537        
538        Iterator<String> itRights = rights.iterator();
539        
540        // VĂ©rifier que nous avons tous les droits
541        while (itRights.hasNext() && hasAllRights)
542        {
543            String right = itRights.next();
544            
545            hasAllRights &= _rightManager.hasRight(user, right, content) == RightResult.RIGHT_ALLOW;
546        }
547        
548        return hasAllRights;
549    }   
550    
551    /**
552     * Configure the tasks.
553     * @param tasksConf the tasks root configuration.
554     * @return the task list.
555     * @throws ConfigurationException if a configuration error occurs.
556     */
557    protected Map<String, Task> _configureTasks(Configuration tasksConf) throws ConfigurationException
558    {
559        Map<String, Task> tasks = new LinkedHashMap<>();
560        for (Configuration taskConf : tasksConf.getChildren("task"))
561        {
562            Task task = new Task();
563            
564            String taskId = taskConf.getAttribute("id", "").trim();
565            
566            task.setId(taskId);
567            task.setLabel(new I18nizableText(null, taskConf.getAttribute("label", "").trim()));
568            
569            int length = taskConf.getAttributeAsInteger("length", 0);
570            task.setLength(length);
571            
572            _configureStep(task, taskConf);
573            
574            _configureAdditional(task, taskConf);
575            
576            tasks.put(taskId, task);
577        }
578        return tasks;
579    }
580    
581    /**
582     * Configure the steps.
583     * @param task the current task
584     * @param taskConf the task configuration.
585     * @throws ConfigurationException if a configuration error occurs.
586     */
587    protected void _configureStep(Task task, Configuration taskConf) throws ConfigurationException
588    {
589        List<TaskStep> steps = new ArrayList<>();
590        for (Configuration stepConf : taskConf.getChildren("step"))
591        {
592            TaskStep step = new TaskStep();
593            
594            String[] rightArray = StringUtils.split(stepConf.getAttribute("rights", ""), ", ");
595            step.setStepIds(_configureStepIds(stepConf));
596            step.setUserContents(stepConf.getAttributeAsBoolean("userContents", false));
597            step.setRights(new HashSet<>(Arrays.asList(rightArray)));
598            
599            steps.add(step);
600        }
601        
602        task.setSteps(steps);
603    }
604    
605    /**
606     * Configure additional configuration
607     * @param task task the current task
608     * @param taskConf the task configuration.
609     * @throws ConfigurationException if a configuration error occurs.
610     */
611    protected void _configureAdditional(Task task, Configuration taskConf) throws ConfigurationException
612    {
613        // Nothing
614    }
615
616    /**
617     * Configure the step IDs.
618     * @param stepConf the step configuration.
619     * @return the step IDs as a Set of Integer.
620     * @throws ConfigurationException If an error occurred
621     */
622    protected Set<Integer> _configureStepIds(Configuration stepConf) throws ConfigurationException
623    {
624        String[] stepIdArray = StringUtils.split(stepConf.getAttribute("id", ""), ", ");
625        Set<Integer> stepIds = new HashSet<>(stepIdArray.length);
626        for (String stepId : stepIdArray)
627        {
628            try
629            {
630                stepIds.add(Integer.valueOf(stepId));
631            }
632            catch (NumberFormatException e)
633            {
634                // Ignore.
635            }
636        }
637        return stepIds;
638    }
639    
640    /**
641     * SAX workflow current step of a {@link Content}
642     * @param ch the content handler.
643     * @param content The content
644     * @throws SAXException if an error occurs while SAXing
645     * @throws RepositoryException if an error occurs while retrieving current step
646     */
647    protected void _saxContentCurrentState(ContentHandler ch, WorkflowAwareContent content) throws SAXException, RepositoryException
648    {
649        int currentStepId = 0;
650        
651        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
652        long workflowId = content.getWorkflowId();
653        
654        List<Step> steps = workflow.getCurrentSteps(workflowId);
655        if (!steps.isEmpty())
656        {
657            currentStepId = steps.get(steps.size() - 1).getStepId();
658        }
659        
660        String workflowName = workflow.getWorkflowName(workflowId);
661        WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
662        StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStepId);
663        
664        I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
665        
666        AttributesImpl attr = new AttributesImpl();
667        attr.addCDATAAttribute("id", Integer.toString(currentStepId));
668        
669        XMLUtils.startElement(ch, "workflow-step", attr);
670        workflowStepName.toSAX(ch);
671        XMLUtils.endElement(ch, "workflow-step");
672        
673        String[] icons = new String[]{"-small", "-medium", "-large"};
674        for (String icon : icons)
675        {
676            if ("application".equals(workflowStepName.getCatalogue()))
677            {
678                XMLUtils.createElement(ch, "workflow-icon" + icon, "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + icon + ".png");
679            }
680            else
681            {
682                String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
683                XMLUtils.createElement(ch, "workflow-icon" + icon, "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + icon + ".png");
684            }
685        }
686    }
687    
688    /**
689     * Class representing a task.
690     */
691    public class Task
692    {
693        /** The task ID. */
694        protected String _id;
695        
696        /** The task label. */
697        protected I18nizableText _label;
698        
699        /** The task step configuration. */
700        protected Collection<TaskStep> _steps;
701        
702        /** The max number of result */
703        protected int _length;
704        
705        /**
706         * Class representing a task.
707         */
708        public Task()
709        {
710            this(new I18nizableText(""), new ArrayList<>());
711        }
712        
713        /**
714         * Class representing a task.
715         * @param label The label of the task
716         * @param steps  The steps of the task
717         */
718        public Task(I18nizableText label, Collection<TaskStep> steps)
719        {
720            _label = label;
721            _steps = new ArrayList<>(steps);
722        }
723
724        /**
725         * Get the id.
726         * @return the id
727         */
728        public String getId()
729        {
730            return _id;
731        }
732
733        /**
734         * Set the id.
735         * @param id the id to set
736         */
737        public void setId(String id)
738        {
739            this._id = id;
740        }
741        
742        /**
743         * Get the length.
744         * @return the length
745         */
746        public int getLength()
747        {
748            return _length;
749        }
750
751        /**
752         * Set the length.
753         * @param length the length to set
754         */
755        public void setLength(int length)
756        {
757            this._length = length;
758        }
759
760        /**
761         * Get the label.
762         * @return the label
763         */
764        public I18nizableText getLabel()
765        {
766            return _label;
767        }
768        
769        /**
770         * Set the label.
771         * @param label the label to set
772         */
773        public void setLabel(I18nizableText label)
774        {
775            this._label = label;
776        }
777        
778        /**
779         * Get the steps.
780         * @return the steps
781         */
782        public Collection<TaskStep> getSteps()
783        {
784            return _steps;
785        }
786
787        /**
788         * Set the steps.
789         * @param steps the steps to set
790         */
791        public void setSteps(Collection<TaskStep> steps)
792        {
793            this._steps = steps;
794        }
795    }
796    
797    /**
798     * Class representing a task step.
799     */
800    public class TaskStep
801    {
802        
803        /** The step ID. */
804        protected Set<Integer> _stepIds;
805        
806        /** Only return user contents. */
807        protected boolean _userContents;
808        
809        /** The rights to have. */
810        protected Set<String> _rights;
811        
812        /**
813         * Construct a TaskStep object.
814         */
815        public TaskStep()
816        {
817            this(new HashSet<>(), false, new HashSet<>());
818        }
819
820        /**
821         * Construct a TaskStep object with parameters.
822         * @param stepIds the step ID.
823         * @param userContents the user contents.
824         * @param rights the rights.
825         */
826        public TaskStep(Set<Integer> stepIds, boolean userContents, Set<String> rights)
827        {
828            super();
829            _stepIds = stepIds;
830            _userContents = userContents;
831            _rights = new HashSet<>(rights);
832        }
833
834        /**
835         * Get the stepId.
836         * @return the stepId
837         */
838        public Set<Integer> getStepIds()
839        {
840            return _stepIds;
841        }
842
843        /**
844         * Set the stepId.
845         * @param stepIds the stepId to set
846         */
847        public void setStepIds(Set<Integer> stepIds)
848        {
849            this._stepIds = stepIds;
850        }
851
852        /**
853         * Get the userContents.
854         * @return the userContents
855         */
856        public boolean getUserContentsOnly()
857        {
858            return _userContents;
859        }
860
861        /**
862         * Set the userContents.
863         * @param userContents the userContents to set
864         */
865        public void setUserContents(boolean userContents)
866        {
867            this._userContents = userContents;
868        }
869
870        /**
871         * Get the rights.
872         * @return the rights
873         */
874        public Set<String> getRights()
875        {
876            return _rights;
877        }
878
879        /**
880         * Set the rights.
881         * @param rights the rights to set
882         */
883        public void setRights(Set<String> rights)
884        {
885            this._rights = rights;
886        }
887        
888    }
889    
890    /**
891     * Compares two contents on the last modified date.
892     */
893    public class LastModifiedDateComparator implements Comparator<Content>
894    {
895        @Override
896        public int compare(Content c1, Content c2)
897        {
898            // The content is before if 
899            return c2.getLastModified().compareTo(c1.getLastModified());
900        }
901    }
902}