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