001/*
002 *  Copyright 2018 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.plugins.odfpilotage.helper;
017
018import java.time.LocalDate;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang3.ArrayUtils;
031
032import org.ametys.cms.ObservationConstants;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.ModifiableDefaultContent;
035import org.ametys.core.observation.Event;
036import org.ametys.core.observation.ObservationManager;
037import org.ametys.core.right.RightManager;
038import org.ametys.core.right.RightManager.RightResult;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.odf.ODFHelper;
042import org.ametys.odf.ProgramItem;
043import org.ametys.odf.program.Program;
044import org.ametys.odf.workflow.ValidateODFContentFunction;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareComposite;
047import org.ametys.runtime.model.ModelItem;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049
050/**
051 * Helper for ODF pilotage status
052 */
053public class PilotageStatusHelper extends AbstractLogEnabled implements Component, Serviceable
054{
055    /** The component role. */
056    public static final String ROLE = PilotageStatusHelper.class.getName();
057    
058    /** The super right for mention validation state */
059    public static final String MENTION_VALIDATION_SUPER_RIGHT_ID = "ODF_Pilotage_Mention_Validated_Super_Rights";
060    
061    /** The super right for orgunit validation state */
062    public static final String ORGUNIT_VALIDATION_SUPER_RIGHT_ID = "ODF_Pilotage_OrgUnit_Validated_Super_Rights";
063    
064    /** The attribute name for the pilotage composite */
065    private static final String __PILOTAGE_COMPOSITE = "pilotage";
066    
067    /** The attribute name for the pilotage status */
068    private static final String __PILOTAGE_STATUS = "pilotage_status";
069    
070    /** The attribute name for the date of the mention validation */
071    private static final String __MENTION_VALIDATION_DATE = "mention_validation_date";
072
073    /** The attribute name for the author of the mention validation */
074    private static final String __MENTION_VALIDATION_AUTHOR = "mention_validation_author";
075
076    /** The attribute name for the comment of the mention validation */
077    private static final String __MENTION_VALIDATION_COMMENT = "mention_validation_comment";
078    
079    /** The attribute name for the date of the orgUnit validation */
080    private static final String __ORGUNIT_VALIDATION_DATE = "orgunit_validation_date";
081
082    /** The attribute name for the author of the orgUnit validation */
083    private static final String __ORGUNIT_VALIDATION_AUTHOR = "orgunit_validation_author";
084
085    /** The attribute name for the comment of the orgUnit validation */
086    private static final String __ORGUNIT_VALIDATION_COMMENT = "orgunit_validation_comment";
087    
088    /** The attribute name for the date of the CFVU validation */
089    private static final String __CFVU_VALIDATION_DATE = "cfvu_validation_date";
090
091    /** The attribute name for the author of the CFVU validation */
092    private static final String __CFVU_VALIDATION_AUTHOR = "cfvu_validation_author";
093
094    /** The attribute name for the comment of the CFVU validation */
095    private static final String __CFVU_VALIDATION_COMMENT = "cfvu_validation_comment";
096    
097    /** The attribute name for the date of the CFVU MCC validation */
098    private static final String __CFVU_MCC_VALIDATION_DATE = "cfvu_mcc_validation_date";
099
100    /** The attribute name for the author of the CFVU MCC validation */
101    private static final String __CFVU_MCC_VALIDATION_AUTHOR = "cfvu_mcc_validation_author";
102
103    /** The attribute name for the comment of the CFVU MCC validation */
104    private static final String __CFVU_MCC_VALIDATION_COMMENT = "cfvu_mcc_validation_comment";
105    
106    /** The odf helper */
107    protected ODFHelper _odfHelper;
108    
109    /** The Ametys object resolver */
110    protected AmetysObjectResolver _resolver;
111    
112    /** The right manager */
113    protected RightManager _rightManager;
114    
115    /** The current user provider */
116    protected CurrentUserProvider _currentUserProvider;
117    
118    /** The observation manager */
119    protected ObservationManager _observationManager;
120    
121    /**
122     * Enumeration for the pilotage status
123     */
124    public enum PilotageStatus
125    {
126        /** State 0 : No status */
127        NONE,
128        /** State 1 : Mention validated */
129        MENTION_VALIDATED,
130        /** State 2 : OrgUnit validated */
131        ORGUNIT_VALIDATED,
132        /** State 3 : CFVU validated */
133        CFVU_VALIDATED,
134        /** State 4 : CFVU MCC validated */
135        CFVU_MCC_VALIDATED
136    }
137    
138    @Override
139    public void service(ServiceManager manager) throws ServiceException
140    {
141        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
142        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
143        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
144        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
145        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
146    }
147    
148    /**
149     * Get all program parent from the program item 
150     * @param programItem the program item
151     * @return the set of program parent
152     */
153    public Set<Program> getParentPrograms(ProgramItem programItem)
154    {
155        Set<Program> parentPrograms = new HashSet<>();
156        if (programItem instanceof Program)
157        {
158            parentPrograms.add((Program) programItem);
159            return parentPrograms;
160        }
161        
162        List<ProgramItem> parents = _odfHelper.getParentProgramItems(programItem);
163        for (ProgramItem parent : parents)
164        {
165            if (parent instanceof Program)
166            {
167                parentPrograms.add((Program) parent);
168            }
169            else
170            {
171                parentPrograms.addAll(getParentPrograms(parent));
172            }
173        }
174        
175        return parentPrograms;
176    }
177    
178    /**
179     * Get all program parent with a pilotage status from the program item 
180     * @param programItem the program item
181     * @return the set of program parent
182     */
183    public Set<Program> getParentProgramsWithPilotageStatus(ProgramItem programItem)
184    {
185        Set<Program> parentPrograms = new HashSet<>();
186        for (Program parent : getParentPrograms(programItem))
187        {
188            PilotageStatus pilotageStatus = getPilotageStatus(parent);
189            if (!pilotageStatus.equals(PilotageStatus.NONE))
190            {
191                parentPrograms.add(parent);
192            }
193        }
194        
195        return parentPrograms;
196    }
197    
198    /**
199     * Return true if the current user has the edit super right depend on the pilotage status of the program
200     * @param program the program
201     * @return true if the user has the super right
202     */
203    public boolean hasEditSuperRight(Program program)
204    {
205        PilotageStatus pilotageStatus = getPilotageStatus(program);
206        
207        UserIdentity user = _currentUserProvider.getUser();
208        switch (pilotageStatus)
209        {
210            case NONE:
211                return true;
212            case MENTION_VALIDATED:
213                return _rightManager.hasRight(user, MENTION_VALIDATION_SUPER_RIGHT_ID, program).equals(RightResult.RIGHT_ALLOW);
214            case ORGUNIT_VALIDATED:
215                return _rightManager.hasRight(user, ORGUNIT_VALIDATION_SUPER_RIGHT_ID, program).equals(RightResult.RIGHT_ALLOW);
216            case CFVU_MCC_VALIDATED:
217            case CFVU_VALIDATED:
218            default:
219                return false;
220        }
221    }
222    
223    /**
224     * Compare two programs depends on their pilotage status
225     * Pilotage status order : NONE lower than MENTION_VALIDATED lower than ORGUNIT_VALIDATED lower than CFVU_VALIDATED lower than CFVU_VALIDATED lower than CFVU_MCC_VALIDATED
226     * -1 if pilotage status of program 1 is lower than pilotage status of program 2
227     * 0 if they have the same pilotage status
228     * 1 if pilotage status of program 1 is higher than pilotage status of program 2
229     * -2 if we don't know
230     * @param program1 program 1
231     * @param program2 progam 2
232     * @return the int compare number
233     */
234    public int comparePilotageStatus(Program program1, Program program2)
235    {
236        PilotageStatus pilotageStatus1 = getPilotageStatus(program1);
237        PilotageStatus pilotageStatus2 = getPilotageStatus(program2);
238        
239        switch (pilotageStatus1)
240        {
241            case NONE:
242                return PilotageStatus.NONE.equals(pilotageStatus2) ? 0 : 1;
243            case MENTION_VALIDATED:
244                if (PilotageStatus.NONE.equals(pilotageStatus2))
245                {
246                    return -1;
247                }
248                else
249                {
250                    return PilotageStatus.MENTION_VALIDATED.equals(pilotageStatus2) ? 0 : 1;
251                }
252            case ORGUNIT_VALIDATED:
253                if (PilotageStatus.NONE.equals(pilotageStatus2) || PilotageStatus.MENTION_VALIDATED.equals(pilotageStatus2))
254                {
255                    return -1;
256                }
257                else
258                {
259                    return PilotageStatus.ORGUNIT_VALIDATED.equals(pilotageStatus2) ? 0 : 1;
260                }
261            case CFVU_VALIDATED:
262                if (PilotageStatus.CFVU_MCC_VALIDATED.equals(pilotageStatus2))
263                {
264                    return 1;
265                }
266                else
267                {
268                    return PilotageStatus.CFVU_VALIDATED.equals(pilotageStatus2) ? 0 : -1;
269                }
270            case CFVU_MCC_VALIDATED:
271                return PilotageStatus.CFVU_MCC_VALIDATED.equals(pilotageStatus2) ? 0 : -1;
272            default:
273                break;
274        }
275        
276        return -2;
277    }
278    
279    /**
280     * Get parent program or it self from program item with the higher pilotage status.
281     * Program with pilotage status NONE are ignored, so can be null if there are no program with active pilotage status.
282     * @param programItem the program item
283     * @return the program parent
284     */
285    public Program getParentProgramWithHigherPilotageStatus(ProgramItem programItem)
286    {
287        Set<Program> parentProgramsWithPilotageStatus = getParentProgramsWithPilotageStatus(programItem);
288        Program parentProgram = null;
289        for (Program program : parentProgramsWithPilotageStatus)
290        {
291            if (parentProgram == null || comparePilotageStatus(parentProgram, program) == 1)
292            {
293                parentProgram = program;
294            }
295        }
296        
297        return parentProgram;
298    }
299    
300    /**
301     * Get the pilotage information status for a client side element.
302     * The button is enabled if the pilotage status of root programs is NONE or if the current user has super edit right on root program of higher status
303     * @param contentId the content id
304     * @return the pilotage information 
305     */
306    public Map<String, Object> getPilotageButtonInfo(String contentId)
307    {
308        Map<String, Object> contentParams = new HashMap<>();
309        Content content = _resolver.resolveById(contentId);
310        if (content instanceof ProgramItem)
311        {
312            ProgramItem programItem = (ProgramItem) content;
313            
314            // Get parent program (or itself) with the higher pilotage status (program with no pilotage status are ignored)
315            Program parentProgramWithHigherPilotageStatus = getParentProgramWithHigherPilotageStatus(programItem);
316            boolean noParentProgramWithActivePilotageStatus = parentProgramWithHigherPilotageStatus == null;
317            
318            contentParams.put("isEnabled", noParentProgramWithActivePilotageStatus || hasEditSuperRight(parentProgramWithHigherPilotageStatus));
319            
320            if (!noParentProgramWithActivePilotageStatus)
321            {
322                // Get parent programs (or itself) which have an active pilotage status (!= NONE)
323                Set<Program> parentPrograms = getParentProgramsWithPilotageStatus(programItem);
324                List<String> programTitles = parentPrograms.stream()
325                              .map(p -> p.getTitle())
326                              .collect(Collectors.toList());
327                
328                contentParams.put("programTitles", programTitles);
329            }
330        }
331        else
332        {
333            contentParams.put("isEnabled", true);
334        }
335        
336        return contentParams;
337    }
338    
339    /**
340     * Get the pilotage status of the content
341     * @param content the content
342     * @return the pilotage status
343     */
344    public PilotageStatus getPilotageStatus(Content content)
345    {
346        String pilotageStatusDataPath = __PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __PILOTAGE_STATUS;
347        String status = content.getValue(pilotageStatusDataPath, false, PilotageStatus.NONE.name());
348        return PilotageStatus.valueOf(status);
349    }
350    
351    /**
352     * Send a notification with the content modified event.
353     * @param content The content to notify on
354     */
355    protected void _notifyPilotageWorkflowModification(Content content)
356    {
357        Map<String, Object> eventParams = new HashMap<>();
358        eventParams.put(ObservationConstants.ARGS_CONTENT, content);
359        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId());
360        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams));
361    }
362    
363    /**
364     * Set the validation attribute (date, login, comment) to the content 
365     * @param content the content
366     * @param validationDate the validation date
367     * @param user the user
368     * @param comment the comment
369     * @param status the pilotage status
370     */
371    public void setValidationAttribute(ModifiableDefaultContent content, LocalDate validationDate, UserIdentity user, String comment, PilotageStatus status)
372    {
373        boolean hasChanges = false;
374        switch (status)
375        {
376            case NONE :
377                // Do nothing
378                break;
379            case MENTION_VALIDATED :
380                hasChanges = setMentionValidationAttribute(content, validationDate, user, comment);
381                break;
382            case ORGUNIT_VALIDATED :
383                hasChanges = setOrgUnitValidationAttribute(content, validationDate, user, comment);
384                break;
385            case CFVU_VALIDATED :
386                hasChanges = setCFVUValidationAttribute(content, validationDate, user, comment);
387                break;
388            case CFVU_MCC_VALIDATED :
389                hasChanges = setCFVUMCCValidationAttribute(content, validationDate, user, comment);
390                break;
391            default :
392                getLogger().error("{} is an unknown pilotage status", status);
393        }
394        
395        if (hasChanges)
396        {
397            _notifyPilotageWorkflowModification(content);
398        }
399    }
400    
401    /**
402     * Remove the validation attribute from the content
403     * @param content the content
404     * @param status the pilotage status
405     */
406    public void removePilotageStatus(ModifiableDefaultContent content, PilotageStatus status)
407    {
408        boolean hasChanges = false;
409        switch (status)
410        {
411            case NONE :
412                // Do nothing
413                break;
414            case MENTION_VALIDATED :
415                hasChanges = removeMentionValidationAttribute(content);
416                break;
417            case ORGUNIT_VALIDATED :
418                hasChanges = removeOrgUnitValidationAttribute(content);
419                break;
420            case CFVU_VALIDATED :
421                hasChanges = removeCFVUValidationAttribute(content);
422                break;
423            case CFVU_MCC_VALIDATED :
424                hasChanges = removeCFVUMCCValidationAttribute(content);
425                break;
426            default :
427                getLogger().error("{} is an unknown pilotage status", status);
428        }
429        
430        if (hasChanges)
431        {
432            _notifyPilotageWorkflowModification(content);
433        }
434    }
435    
436    /**
437     * Set the validation attribute for 'mention validated' state
438     * @param content the content
439     * @param validationDate the validation date
440     * @param user the user
441     * @param comment the comment
442     * @return <code>true</code> if the content has changed.
443     */
444    public boolean setMentionValidationAttribute(ModifiableDefaultContent content, LocalDate validationDate, UserIdentity user, String comment)
445    {
446        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
447        composite.setValue(__MENTION_VALIDATION_DATE, validationDate);
448        composite.setValue(__MENTION_VALIDATION_AUTHOR, user);
449        composite.setValue(__MENTION_VALIDATION_COMMENT, comment);
450        
451        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.MENTION_VALIDATED.name());
452        
453        return saveContent(content);
454    }
455    
456    
457    /**
458     * Remove validation attribute for 'mention validated' state
459     * @param content the content
460     * @return <code>true</code> if the content has changed.
461     */
462    public boolean removeMentionValidationAttribute(ModifiableDefaultContent content)
463    {
464        // Remove the first step
465        return removePilotageWorkflow(content);
466    }
467    
468    /**
469     * Set the validation attribute for 'orgunit validated' state
470     * @param content the content
471     * @param validationDate the validation date
472     * @param user the user
473     * @param comment the comment
474     * @return <code>true</code> if the content has changed.
475     */
476    public boolean setOrgUnitValidationAttribute(ModifiableDefaultContent content, LocalDate validationDate, UserIdentity user, String comment)
477    {
478        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
479        composite.setValue(__ORGUNIT_VALIDATION_DATE, validationDate);
480        composite.setValue(__ORGUNIT_VALIDATION_AUTHOR, user);
481        composite.setValue(__ORGUNIT_VALIDATION_COMMENT, comment);
482        
483        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.ORGUNIT_VALIDATED.name());
484
485        return saveContent(content);
486    }
487    
488    /**
489     * Remove validation attribute for 'orgunit validated' state
490     * @param content the content
491     * @return <code>true</code> if the content has changed.
492     */
493    public boolean removeOrgUnitValidationAttribute(ModifiableDefaultContent content)
494    {
495        ModifiableModelAwareComposite compositeMetadata = content.getComposite(__PILOTAGE_COMPOSITE, true);
496        compositeMetadata.removeValue(__ORGUNIT_VALIDATION_DATE);
497        compositeMetadata.removeValue(__ORGUNIT_VALIDATION_AUTHOR);
498        compositeMetadata.removeValue(__ORGUNIT_VALIDATION_COMMENT);
499        
500        compositeMetadata.setValue(__PILOTAGE_STATUS, PilotageStatus.MENTION_VALIDATED.name());
501
502        return saveContent(content);
503    }
504    
505    /**
506     * Set the validation attribute for 'CFVU validated' state
507     * @param content the content
508     * @param validationDate the validation date
509     * @param user the login
510     * @param comment the comment
511     * @return <code>true</code> if the content has changed.
512     */
513    public boolean setCFVUValidationAttribute(ModifiableDefaultContent content, LocalDate validationDate, UserIdentity user, String comment)
514    {
515        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
516        composite.setValue(__CFVU_VALIDATION_DATE, validationDate);
517        composite.setValue(__CFVU_VALIDATION_AUTHOR, user);
518        composite.setValue(__CFVU_VALIDATION_COMMENT, comment);
519        
520        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.CFVU_VALIDATED.name());
521
522        return saveContent(content);
523    }
524    
525    /**
526     * Remove validation attribute for 'CFVU validated' state
527     * @param content the content
528     * @return <code>true</code> if the content has changed.
529     */
530    public boolean removeCFVUValidationAttribute(ModifiableDefaultContent content)
531    {
532        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
533        composite.removeValue(__CFVU_VALIDATION_DATE);
534        composite.removeValue(__CFVU_VALIDATION_AUTHOR);
535        composite.removeValue(__CFVU_VALIDATION_COMMENT);
536        
537        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.ORGUNIT_VALIDATED.name());
538
539        return saveContent(content);
540    }
541    
542    /**
543     * Set the validation attribute for 'CFVU MCC validated' state
544     * @param content the content
545     * @param validationDate the validation date
546     * @param user the user
547     * @param comment the comment
548     * @return <code>true</code> if the content has changed.
549     */
550    public boolean setCFVUMCCValidationAttribute(ModifiableDefaultContent content, LocalDate validationDate, UserIdentity user, String comment)
551    {
552        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
553        composite.setValue(__CFVU_MCC_VALIDATION_DATE, validationDate);
554        composite.setValue(__CFVU_MCC_VALIDATION_AUTHOR, user);
555        composite.setValue(__CFVU_MCC_VALIDATION_COMMENT, comment);
556        
557        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.CFVU_MCC_VALIDATED.name());
558
559        return saveContent(content);
560    }
561    
562    /**
563     * Remove validation attribute for 'CFVU MCC validated' state
564     * @param content the content
565     * @return <code>true</code> if the content has changed.
566     */
567    public boolean removeCFVUMCCValidationAttribute(ModifiableDefaultContent content)
568    {
569        ModifiableModelAwareComposite composite = content.getComposite(__PILOTAGE_COMPOSITE, true);
570        composite.removeValue(__CFVU_MCC_VALIDATION_DATE);
571        composite.removeValue(__CFVU_MCC_VALIDATION_AUTHOR);
572        composite.removeValue(__CFVU_MCC_VALIDATION_COMMENT);
573        
574        composite.setValue(__PILOTAGE_STATUS, PilotageStatus.CFVU_VALIDATED.name());
575
576        return saveContent(content);
577    }
578    
579    /**
580     * Get mention validation date
581     * @param content the content
582     * @return the validation date
583     */
584    public LocalDate getMentionValidationDate(Content content)
585    {
586        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __MENTION_VALIDATION_DATE);
587    }
588    
589    /**
590     * Get mention validation comment
591     * @param content the content
592     * @return the validation comment
593     */
594    public String getMentionValidationComment(Content content)
595    {
596        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __MENTION_VALIDATION_COMMENT);
597    }
598    
599    /**
600     * Get mention validation author
601     * @param content the content
602     * @return the validation author
603     */
604    public UserIdentity getMentionValidationAuthor(Content content)
605    {
606        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __MENTION_VALIDATION_AUTHOR);
607    }
608    
609    /**
610     * Get orgUnit validation date
611     * @param content the content
612     * @return the validation date
613     */
614    public LocalDate getOrgUnitValidationDate(Content content)
615    {
616        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __ORGUNIT_VALIDATION_DATE);
617    }
618    
619    /**
620     * Get orgUnit validation comment
621     * @param content the content
622     * @return the validation comment
623     */
624    public String getOrgUnitValidationComment(Content content)
625    {
626        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __ORGUNIT_VALIDATION_COMMENT);
627    }
628    
629    /**
630     * Get orgUnit validation author
631     * @param content the content
632     * @return the validation author
633     */
634    public UserIdentity getOrgUnitValidationAuthor(Content content)
635    {
636        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __ORGUNIT_VALIDATION_AUTHOR);
637    }
638    
639    /**
640     * Get CFVU validation date
641     * @param content the content
642     * @return the validation date
643     */
644    public LocalDate getCFVUValidationDate(Content content)
645    {
646        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_VALIDATION_DATE);
647    }
648    
649    /**
650     * Get CFVU validation comment
651     * @param content the content
652     * @return the validation comment
653     */
654    public String getCFVUValidationComment(Content content)
655    {
656        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_VALIDATION_COMMENT);
657    }
658    
659    /**
660     * Get CFVU validation author
661     * @param content the content
662     * @return the validation author
663     */
664    public UserIdentity getCFVUValidationAuthor(Content content)
665    {
666        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_VALIDATION_AUTHOR);
667    }
668    
669    /**
670     * Get CFVU MMC validation date
671     * @param content the content
672     * @return the validation date
673     */
674    public LocalDate getCFVUMCCValidationDate(Content content)
675    {
676        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_MCC_VALIDATION_DATE);
677    }
678    
679    /**
680     * Get CFVU MCC validation comment
681     * @param content the content
682     * @return the validation comment
683     */
684    public String getCFVUMCCValidationComment(Content content)
685    {
686        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_MCC_VALIDATION_COMMENT);
687    }
688    
689    /**
690     * Get CFVU MCC validation author
691     * @param content the content
692     * @return the validation author
693     */
694    public UserIdentity getCFVUMCCValidationAuthor(Content content)
695    {
696        return content.getValue(__PILOTAGE_COMPOSITE + ModelItem.ITEM_PATH_SEPARATOR + __CFVU_MCC_VALIDATION_AUTHOR);
697    }
698    
699    /**
700     * Remove the pilotage workflow metadata.
701     * @param content The content to clean
702     * @return <code>true</code> if the content has changed
703     */
704    public boolean removePilotageWorkflow(ModifiableDefaultContent content)
705    {
706        if (content.hasValue(__PILOTAGE_COMPOSITE))
707        {
708            content.removeValue(__PILOTAGE_COMPOSITE);
709            return saveContent(content);
710        }
711        
712        return false;
713    }
714    
715    /**
716     * Save the content if needed, add a version (checkpoint) and move the Live label if the last version was validated.
717     * @param content The content to save
718     * @return <code>true</code> if the content has changed
719     */
720    protected boolean saveContent(ModifiableDefaultContent content)
721    {
722        if (content.needsSave())
723        {
724            boolean currentVersionIsLive = ArrayUtils.contains(content.getLabels(), ValidateODFContentFunction.VALID_LABEL);
725            
726            content.saveChanges();
727            content.checkpoint();
728            
729            // Move the Live label if the last version was validated.
730            if (currentVersionIsLive)
731            {
732                content.addLabel(ValidateODFContentFunction.VALID_LABEL, true);
733            }
734            
735            return true;
736        }
737        
738        return false;
739    }
740}