001/*
002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others.
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 *
016 * Contributors:
017 *    Mariana Cedica
018 *
019 * $Id$
020 */
021package org.nuxeo.ecm.platform.routing.web;
022
023import static org.jboss.seam.ScopeType.CONVERSATION;
024import static org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager.CURRENT_DOCUMENT_SELECTION;
025
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031
032import javax.faces.convert.Converter;
033
034import org.apache.commons.lang3.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.jboss.seam.ScopeType;
038import org.jboss.seam.annotations.Factory;
039import org.jboss.seam.annotations.In;
040import org.jboss.seam.annotations.Install;
041import org.jboss.seam.annotations.Name;
042import org.jboss.seam.annotations.Observer;
043import org.jboss.seam.annotations.Scope;
044import org.jboss.seam.annotations.web.RequestParameter;
045import org.jboss.seam.contexts.Contexts;
046import org.jboss.seam.core.Events;
047import org.jboss.seam.faces.FacesMessages;
048import org.jboss.seam.international.StatusMessage;
049import org.nuxeo.ecm.core.api.CoreSession;
050import org.nuxeo.ecm.core.api.DocumentModel;
051import org.nuxeo.ecm.core.api.DocumentModelList;
052import org.nuxeo.ecm.core.api.DocumentRef;
053import org.nuxeo.ecm.core.api.IdRef;
054import org.nuxeo.ecm.core.api.NuxeoPrincipal;
055import org.nuxeo.ecm.core.api.PathRef;
056import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
057import org.nuxeo.ecm.core.api.event.CoreEventConstants;
058import org.nuxeo.ecm.platform.routing.api.DocumentRoute;
059import org.nuxeo.ecm.platform.routing.api.DocumentRouteElement;
060import org.nuxeo.ecm.platform.routing.api.DocumentRouteTableElement;
061import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants;
062import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants.ExecutionTypeValues;
063import org.nuxeo.ecm.platform.routing.api.DocumentRoutingService;
064import org.nuxeo.ecm.platform.routing.api.LockableDocumentRoute;
065import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteAlredayLockedException;
066import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteNotLockedException;
067import org.nuxeo.ecm.platform.routing.core.api.DocumentRoutingEngineService;
068import org.nuxeo.ecm.platform.routing.core.impl.GraphRoute;
069import org.nuxeo.ecm.platform.task.Task;
070import org.nuxeo.ecm.platform.task.TaskEventNames;
071import org.nuxeo.ecm.platform.task.TaskService;
072import org.nuxeo.ecm.platform.types.TypeManager;
073import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
074import org.nuxeo.ecm.platform.ui.web.api.WebActions;
075import org.nuxeo.ecm.webapp.action.TypesTool;
076import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager;
077import org.nuxeo.ecm.webapp.edit.lock.LockActions;
078import org.nuxeo.ecm.webapp.helpers.EventManager;
079import org.nuxeo.ecm.webapp.helpers.EventNames;
080import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
081import org.nuxeo.runtime.api.Framework;
082
083/**
084 * Actions for current document route
085 *
086 * @author Mariana Cedica
087 */
088@Scope(CONVERSATION)
089@Name("routingActions")
090@Install(precedence = Install.FRAMEWORK)
091public class DocumentRoutingActionsBean implements Serializable {
092    private static final long serialVersionUID = 1L;
093
094    private static final Log log = LogFactory.getLog(DocumentRoutingActionsBean.class);
095
096    public static final String SOURCE_DOC_NAME = "source_doc_name";
097
098    public static final String ROUTE_DOCUMENT_REF = "route_doc_ref";
099
100    @In(required = true, create = true)
101    protected NavigationContext navigationContext;
102
103    @In(create = true, required = false)
104    protected CoreSession documentManager;
105
106    @In(create = true, required = false)
107    protected FacesMessages facesMessages;
108
109    @In(create = true)
110    protected WebActions webActions;
111
112    @In(create = true)
113    protected LockActions lockActions;
114
115    @In(create = true, required = false)
116    protected TypesTool typesTool;
117
118    @In(create = true)
119    protected ResourcesAccessor resourcesAccessor;
120
121    @In(create = true)
122    protected TypeManager typeManager;
123
124    @In(create = true)
125    protected EventManager eventManager;
126
127    @In(required = true, create = true)
128    protected NuxeoPrincipal currentUser;
129
130    @In(create = true)
131    protected List<DocumentModel> relatedRoutes;
132
133    @In(create = true)
134    protected RelatedRouteActionBean relatedRouteAction;
135
136    @In(create = true)
137    protected DocumentsListsManager documentsListsManager;
138
139    @RequestParameter("stepId")
140    protected String stepId;
141
142    protected String relatedRouteModelDocumentId;
143
144    protected String docWithAttachedRouteId;
145
146    protected String hiddenSourceDocId;
147
148    protected String hiddenDocOrder;
149
150    enum StepOrder {
151        before, in, after
152    }
153
154    public DocumentRoutingService getDocumentRoutingService() {
155        return Framework.getService(DocumentRoutingService.class);
156    }
157
158    @Observer(value = { EventNames.DOCUMENT_CHANGED, EventNames.DOCUMENT_SELECTION_CHANGED })
159    public void resetRelatedRouteDocumentId() {
160        relatedRouteModelDocumentId = null;
161    }
162
163    public boolean isRoutable() {
164        return getDocumentRoutingService().isRoutable(navigationContext.getCurrentDocument());
165    }
166
167    public String startRoute() {
168        DocumentModel currentDocument = navigationContext.getCurrentDocument();
169        DocumentRoute currentRoute = currentDocument.getAdapter(DocumentRoute.class);
170        if (currentRoute == null) {
171            log.warn("Current document is not a workflow model");
172            facesMessages.add(StatusMessage.Severity.ERROR,
173                    resourcesAccessor.getMessages().get("label.document.routing.no.workflow"));
174            return null;
175        }
176        getDocumentRoutingService().createNewInstance(currentDocument.getName(), currentRoute.getAttachedDocuments(),
177                documentManager, true);
178        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentDocument);
179        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_NEW_STARTED);
180        webActions.resetTabList();
181        return null;
182    }
183
184    /**
185     * Gets the first related route.
186     * <p>
187     * When called on an actual route or route element, the route is returned.
188     * <p>
189     * When called on a regular document, the routing service is queried to get the routes which have the current
190     * document attached.
191     * <p>
192     * When dealing with a regular document, this is DEPRECATED as several graph routes may be related to the current
193     * document (for instance in the case of sub-workflows). Use {@link #getRelatedRoutes} instead.
194     */
195    public DocumentRoute getRelatedRoute() {
196        // try to see if actually the current document is a route
197        DocumentModel currentDocument = navigationContext.getCurrentDocument();
198
199        if (currentDocument.hasFacet(DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_FACET)) {
200            docWithAttachedRouteId = null;
201            return currentDocument.getAdapter(DocumentRoute.class);
202        }
203        // try to see if the current document is a routeElement
204        DocumentRouteElement relatedRouteElement = currentDocument.getAdapter(DocumentRouteElement.class);
205        if (relatedRouteElement != null) {
206            docWithAttachedRouteId = null;
207            return relatedRouteElement.getDocumentRoute(documentManager);
208        }
209        // else we must be in a document attached to a route
210        List<DocumentRoute> routes = getRelatedRoutes();
211        if (routes.isEmpty()) {
212            return null;
213        }
214        docWithAttachedRouteId = currentDocument.getId();
215        return routes.get(0);
216    }
217
218    /**
219     * Gets the list of routes related to the current document, by querying the routing service.
220     *
221     * @return the list of routes (may be empty)
222     * @since 5.7.2
223     */
224    public List<DocumentRoute> getRelatedRoutes() {
225        queryForRelatedRoutes();
226        List<DocumentRoute> routes = new ArrayList<DocumentRoute>(relatedRoutes.size());
227        for (DocumentModel doc : relatedRoutes) {
228            routes.add(doc.getAdapter(DocumentRoute.class));
229        }
230        return routes;
231    }
232
233    protected void queryForRelatedRoutes() {
234        if (relatedRoutes == null) {
235            relatedRoutes = relatedRouteAction.findRelatedRoute();
236        }
237    }
238
239    /**
240     * Cancels the first workflow found on the current document
241     */
242    public String cancelRoute() {
243        List<DocumentRoute> routes = getRelatedRoutes();
244        if (routes.size() == 0) {
245            log.error("No workflow to cancel");
246            return null;
247        }
248        DocumentRoute route = routes.get(0);
249        Framework.getService(DocumentRoutingEngineService.class).cancel(route, documentManager);
250        // force computing of tabs
251        webActions.resetTabList();
252        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_CANCELED);
253        Contexts.removeFromAllContexts("relatedRoutes");
254        documentManager.save();
255        return navigationContext.navigateToDocument(navigationContext.getCurrentDocument());
256    }
257
258    public void saveRouteAsNewInstance() {
259        getDocumentRoutingService().saveRouteAsNewModel(getRelatedRoute(), documentManager);
260        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED);
261        facesMessages.add(StatusMessage.Severity.INFO,
262                resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.route_duplicated"));
263    }
264
265    public void saveSelectedRouteAsNewInstance() {
266        List<DocumentModel> docs = documentsListsManager.getWorkingList(CURRENT_DOCUMENT_SELECTION);
267        if (!docs.isEmpty()) {
268            DocumentRoute route;
269            for (DocumentModel doc : docs) {
270                route = doc.getAdapter(DocumentRoute.class);
271                if (route != null) {
272                    getDocumentRoutingService().saveRouteAsNewModel(route, documentManager);
273                }
274            }
275        }
276        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED);
277        facesMessages.add(StatusMessage.Severity.INFO,
278                resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.selected_route_duplicated"));
279    }
280
281    public boolean getCanDuplicateRouteInstance() {
282        List<DocumentModel> docs = documentsListsManager.getWorkingList(CURRENT_DOCUMENT_SELECTION);
283        if (docs.isEmpty()) {
284            return false;
285        }
286        for (DocumentModel doc : docs) {
287            if (!doc.hasFacet(DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_FACET)) {
288                return false;
289            }
290        }
291        return true;
292    }
293
294    public String validateRouteModel() {
295        DocumentRoute currentRouteModel = getRelatedRoute();
296        try {
297            getDocumentRoutingService().validateRouteModel(currentRouteModel, documentManager);
298        } catch (DocumentRouteNotLockedException e) {
299            facesMessages.add(StatusMessage.Severity.WARN,
300                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.not.locked"));
301            return null;
302        }
303        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentRouteModel.getDocument());
304        getDocumentRoutingService().unlockDocumentRouteUnrestrictedSession(currentRouteModel, documentManager);
305        return null;
306    }
307
308    /**
309     * @deprecated since 5.9.2 - Use only routes of type 'graph'
310     */
311    @Deprecated
312    protected List<DocumentRouteTableElement> computeRouteElements() {
313        DocumentModel currentDocument = navigationContext.getCurrentDocument();
314        DocumentRoute currentRoute = currentDocument.getAdapter(DocumentRoute.class);
315        return getElements(currentRoute);
316    }
317
318    /**
319     * @deprecated since 5.9.2 - Use only routes of type 'graph'
320     */
321    @Deprecated
322    protected List<DocumentRouteTableElement> computeRelatedRouteElements() {
323        if (relatedRoutes.isEmpty()) {
324            return new ArrayList<DocumentRouteTableElement>();
325        }
326        DocumentModel relatedRouteDocumentModel = documentManager.getDocument(new IdRef(relatedRoutes.get(0).getId()));
327        DocumentRoute currentRoute = relatedRouteDocumentModel.getAdapter(DocumentRoute.class);
328        return getElements(currentRoute);
329    }
330
331    /**
332     * @deprecated since 5.9.2 - Use only routes of type 'graph'
333     */
334    @Deprecated
335    protected List<DocumentRouteTableElement> getElements(DocumentRoute currentRoute) {
336        return getDocumentRoutingService().getRouteElements(currentRoute, documentManager);
337    }
338
339    /**
340     * Check if the related route to this case is started (ready or running) or no
341     */
342    public boolean hasRelatedRoute() {
343        queryForRelatedRoutes();
344        return !relatedRoutes.isEmpty();
345    }
346
347    public String startRouteRelatedToCurrentDocument() {
348        DocumentRoute route = getRelatedRoute();
349        // check relatedRoutedoc id
350        if (!StringUtils.isEmpty(relatedRouteModelDocumentId)) {
351            DocumentModel model = documentManager.getDocument(new IdRef(relatedRouteModelDocumentId));
352            route = model.getAdapter(DocumentRoute.class);
353        }
354        if (route == null) {
355            facesMessages.add(StatusMessage.Severity.WARN,
356                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.no.valid.route"));
357            return null;
358        }
359
360        List<String> documentIds = new ArrayList<String>();
361        documentIds.add(navigationContext.getCurrentDocument().getId());
362        route.setAttachedDocuments(documentIds);
363        getDocumentRoutingService().createNewInstance(route.getDocument().getName(), documentIds, documentManager, true);
364        Events.instance().raiseEvent(TaskEventNames.WORKFLOW_NEW_STARTED);
365        webActions.resetTabList();
366        return null;
367    }
368
369    /**
370     * returns true if the routeStarted on the current Document is editable (is Ready )
371     */
372    public boolean routeRelatedToCurrentDocumentIsRunning() {
373        DocumentRoute route = getRelatedRoute();
374        if (route == null) {
375            return false;
376        }
377        return route.isRunning();
378    }
379
380    public String getTypeDescription(DocumentRouteTableElement localizable) {
381        return depthFormatter(localizable.getDepth(), localizable.getElement().getDocument().getType());
382    }
383
384    private String depthFormatter(int depth, String type) {
385        StringBuilder depthFormatter = new StringBuilder();
386        for (int i = 0; i < depth - 1; i++) {
387            depthFormatter.append("__");
388        }
389        depthFormatter.append(type);
390        return depthFormatter.toString();
391    }
392
393    public Converter getDocumentModelConverter() {
394        return new DocumentModelConvertor(documentManager);
395    }
396
397    public boolean isStep(DocumentModel doc) {
398        return (doc.hasFacet(DocumentRoutingConstants.ROUTE_STEP_FACET));
399    }
400
401    public boolean currentRouteModelIsDraft() {
402        DocumentModel relatedRouteModel = navigationContext.getCurrentDocument();
403        DocumentRoute routeModel = relatedRouteModel.getAdapter(DocumentRoute.class);
404        if (routeModel == null) {
405            return false;
406        }
407        return routeModel.isDraft();
408    }
409
410    @Deprecated
411    // @deprecated since 5.9.2 - Use only routes of type 'graph'
412    public String removeStep() {
413        boolean alreadyLockedByCurrentUser = false;
414        DocumentRoute routeModel = getRelatedRoute();
415        if (getDocumentRoutingService().isLockedByCurrentUser(routeModel, documentManager)) {
416            alreadyLockedByCurrentUser = true;
417        } else {
418            if (lockRoute(routeModel) == null) {
419                return null;
420            }
421        }
422        if (StringUtils.isEmpty(stepId)) {
423            return null;
424        }
425        DocumentRef docRef = new IdRef(stepId);
426        DocumentModel stepToDelete = documentManager.getDocument(docRef);
427        try {
428            getDocumentRoutingService().removeRouteElement(stepToDelete.getAdapter(DocumentRouteElement.class),
429                    documentManager);
430        } catch (DocumentRouteNotLockedException e) {
431            facesMessages.add(StatusMessage.Severity.WARN,
432                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.not.locked"));
433            return null;
434        }
435        Contexts.removeFromAllContexts("relatedRoutes");
436        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, routeModel.getDocument());
437        // Release the lock only when currentUser had locked it before
438        // entering this method.
439        if (!alreadyLockedByCurrentUser) {
440            getDocumentRoutingService().unlockDocumentRoute(routeModel, documentManager);
441        }
442        return null;
443    }
444
445    /**
446     * Returns true if the givenDoc is a step that can be edited
447     *
448     * @deprecated since 5.9.2 - Use only routes of type 'graph'
449     */
450    @Deprecated
451    public boolean isEditableStep(DocumentModel stepDoc) {
452        DocumentRouteElement stepElement = stepDoc.getAdapter(DocumentRouteElement.class);
453        // if fork, is not simple editable step
454        if (stepDoc.hasFacet("Folderish")) {
455            return false;
456        }
457        return stepElement.isModifiable();
458    }
459
460    /**
461     * Returns true if the givenDoc is an routeElement that can be edited
462     *
463     * @deprecated since 5.9.2 - Use only routes of type 'graph'
464     */
465    @Deprecated
466    public boolean isEditableRouteElement(DocumentModel stepDoc) {
467        DocumentRouteElement stepElement = stepDoc.getAdapter(DocumentRouteElement.class);
468        return stepElement.isModifiable();
469    }
470
471    @Factory(value = "currentRouteLockedByCurrentUser", scope = ScopeType.EVENT)
472    public boolean isCurrentRouteLockedByCurrentUser() {
473        return getDocumentRoutingService().isLockedByCurrentUser(getRelatedRoute(), documentManager);
474    }
475
476    public boolean isCurrentRouteLocked() {
477        LockableDocumentRoute lockableRoute = getRelatedRoute().getDocument().getAdapter(LockableDocumentRoute.class);
478        return lockableRoute.isLocked(documentManager);
479    }
480
481    public boolean canUnlockRoute() {
482        return Boolean.TRUE.equals(lockActions.getCanUnlockDoc(getRelatedRoute().getDocument()));
483    }
484
485    public boolean canLockRoute() {
486        return Boolean.TRUE.equals(lockActions.getCanLockDoc(getRelatedRoute().getDocument()));
487    }
488
489    public Map<String, Serializable> getCurrentRouteLockDetails() {
490        return lockActions.getLockDetails(getRelatedRoute().getDocument());
491    }
492
493    public String lockCurrentRoute() {
494        DocumentRoute docRouteElement = getRelatedRoute();
495        return lockRoute(docRouteElement);
496    }
497
498    protected String lockRoute(DocumentRoute docRouteElement) {
499        try {
500            getDocumentRoutingService().lockDocumentRoute(docRouteElement.getDocumentRoute(documentManager),
501                    documentManager);
502        } catch (DocumentRouteAlredayLockedException e) {
503            facesMessages.add(StatusMessage.Severity.WARN,
504                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.already.locked"));
505            return null;
506        }
507        return null;
508    }
509
510    public String unlockCurrentRoute() {
511        DocumentRoute route = getRelatedRoute();
512        getDocumentRoutingService().unlockDocumentRoute(route, documentManager);
513        return null;
514    }
515
516    public boolean isEmptyFork(DocumentModel forkDoc) {
517        return forkDoc.hasFacet("Folderish") && !documentManager.hasChildren(forkDoc.getRef());
518    }
519
520    public String editStep() {
521        if (StringUtils.isEmpty(stepId)) {
522            return null;
523        }
524        DocumentRef stepRef = new IdRef(stepId);
525        DocumentModel currentDoc = navigationContext.getCurrentDocument();
526        if (currentDoc.getAdapter(DocumentRoute.class) == null) {
527            setDocWithAttachedRouteId(currentDoc.getId());
528        }
529        return navigationContext.navigateToDocument(documentManager.getDocument(stepRef), "edit");
530    }
531
532    public String updateRouteElement() {
533        boolean alreadyLockedByCurrentUser = false;
534        DocumentModel currentDocument = navigationContext.getCurrentDocument();
535        DocumentRouteElement docRouteElement = currentDocument.getAdapter(DocumentRouteElement.class);
536        DocumentRoute route = docRouteElement.getDocumentRoute(documentManager);
537        if (getDocumentRoutingService().isLockedByCurrentUser(route, documentManager)) {
538            alreadyLockedByCurrentUser = true;
539        } else {
540            if (lockRoute(route) == null) {
541                return null;
542            }
543        }
544        try {
545            getDocumentRoutingService().updateRouteElement(docRouteElement, documentManager);
546        } catch (DocumentRouteNotLockedException e) {
547            facesMessages.add(StatusMessage.Severity.WARN,
548                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.already.locked"));
549            return null;
550        }
551        navigationContext.invalidateCurrentDocument();
552        facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("document_modified"),
553                resourcesAccessor.getMessages().get(currentDocument.getType()));
554        EventManager.raiseEventsOnDocumentChange(currentDocument);
555        // Release the lock only when currentUser had locked it before
556        // entering this method.
557        if (!alreadyLockedByCurrentUser) {
558            getDocumentRoutingService().unlockDocumentRoute(route, documentManager);
559        }
560        if (docWithAttachedRouteId == null) {
561            return webActions.setCurrentTabAndNavigate(docRouteElement.getDocumentRoute(documentManager).getDocument(),
562                    "TAB_DOCUMENT_ROUTE_ELEMENTS");
563        }
564
565        setRelatedRouteWhenNavigateBackToCase();
566        return webActions.setCurrentTabAndNavigate(documentManager.getDocument(new IdRef(docWithAttachedRouteId)),
567                "TAB_CASE_MANAGEMENT_VIEW_RELATED_ROUTE");
568    }
569
570    public String goBackToRoute() {
571        DocumentModel currentDocument = navigationContext.getCurrentDocument();
572        DocumentRouteElement docRouteElement = currentDocument.getAdapter(DocumentRouteElement.class);
573        return webActions.setCurrentTabAndNavigate(docRouteElement.getDocumentRoute(documentManager).getDocument(),
574                "TAB_DOCUMENT_ROUTE_ELEMENTS");
575    }
576
577    /**
578     * @deprecated since 5.9.2 - Use only routes of type 'graph'
579     */
580    @Deprecated
581    public String createRouteElement(String typeName) {
582        DocumentModel currentDocument = navigationContext.getCurrentDocument();
583        DocumentRef routeRef = currentDocument.getRef();
584        DocumentRef sourceDocRef = new IdRef(hiddenSourceDocId);
585        DocumentModel sourceDoc = documentManager.getDocument(sourceDocRef);
586        String sourceDocName = null;
587        String parentPath = null;
588        if (StepOrder.in.toString().equals(hiddenDocOrder)) {
589            parentPath = sourceDoc.getPathAsString();
590        } else {
591            DocumentModel parentDoc = documentManager.getParentDocument(sourceDocRef);
592            parentPath = parentDoc.getPathAsString();
593            if (StepOrder.before.toString().equals(hiddenDocOrder)) {
594                sourceDocName = sourceDoc.getName();
595            } else {
596                DocumentModelList orderedChilds = getDocumentRoutingService().getOrderedRouteElement(parentDoc.getId(),
597                        documentManager);
598                int selectedDocumentIndex = orderedChilds.indexOf(sourceDoc);
599                int nextIndex = selectedDocumentIndex + 1;
600                if (nextIndex >= orderedChilds.size()) {
601                    sourceDocName = null;
602                } else {
603                    sourceDocName = orderedChilds.get(nextIndex).getName();
604                }
605            }
606        }
607        org.nuxeo.ecm.platform.types.Type docType = typeManager.getType(typeName);
608        // we cannot use typesTool as intermediary since the DataModel callback
609        // will alter whatever type we set
610        typesTool.setSelectedType(docType);
611        DocumentModel changeableDocument = documentManager.createDocumentModel(typeName);
612        changeableDocument.putContextData(CoreEventConstants.PARENT_PATH, parentPath);
613        changeableDocument.putContextData(SOURCE_DOC_NAME, sourceDocName);
614        changeableDocument.putContextData(ROUTE_DOCUMENT_REF, routeRef);
615        navigationContext.setChangeableDocument(changeableDocument);
616        return "create_route_element";
617    }
618
619    /**
620     * Moves the step in the parent container in the specified direction. If the step is in a parallel container, it
621     * can't be moved. A step can't be moved before a step already done or running. Assumed that the route is already
622     * locked to have this action available, so no check is done.
623     *
624     * @deprecated since 5.9.2 - Use only routes of type 'graph'
625     */
626    @Deprecated
627    public String moveRouteElement(String direction) {
628        if (StringUtils.isEmpty(stepId)) {
629            return null;
630        }
631        DocumentModel routeElementDocToMove = documentManager.getDocument(new IdRef(stepId));
632        DocumentModel parentDoc = documentManager.getDocument(routeElementDocToMove.getParentRef());
633        ExecutionTypeValues executionType = ExecutionTypeValues.valueOf((String) parentDoc.getPropertyValue(DocumentRoutingConstants.EXECUTION_TYPE_PROPERTY_NAME));
634        if (!DocumentRoutingConstants.ExecutionTypeValues.serial.equals(executionType)) {
635            facesMessages.add(
636                    StatusMessage.Severity.WARN,
637                    resourcesAccessor.getMessages().get(
638                            "feedback.casemanagement.document.route.cant.move.steps.in.parallel.container"));
639            return null;
640        }
641        DocumentModelList orderedChilds = getDocumentRoutingService().getOrderedRouteElement(parentDoc.getId(),
642                documentManager);
643        int selectedDocumentIndex = orderedChilds.indexOf(routeElementDocToMove);
644        if (DocumentRoutingWebConstants.MOVE_STEP_UP.equals(direction)) {
645            if (selectedDocumentIndex == 0) {
646                facesMessages.add(
647                        StatusMessage.Severity.WARN,
648                        resourcesAccessor.getMessages().get(
649                                "feedback.casemanagement.document.route.already.first.step.in.container"));
650                return null;
651            }
652            routeElementDocToMove.getAdapter(DocumentRouteElement.class);
653            DocumentModel stepMoveBefore = orderedChilds.get(selectedDocumentIndex - 1);
654            DocumentRouteElement stepElementMoveBefore = stepMoveBefore.getAdapter(DocumentRouteElement.class);
655            if (stepElementMoveBefore.isRunning()) {
656                facesMessages.add(
657                        StatusMessage.Severity.WARN,
658                        resourcesAccessor.getMessages().get(
659                                "feedback.casemanagement.document.route.cant.move.step.before.already.running.step"));
660                return null;
661            }
662            if (!stepElementMoveBefore.isModifiable()) {
663                facesMessages.add(
664                        StatusMessage.Severity.WARN,
665                        resourcesAccessor.getMessages().get(
666                                "feedback.casemanagement.document.route.cant.move.step.after.no.modifiable.step"));
667                return null;
668            }
669            documentManager.orderBefore(parentDoc.getRef(), routeElementDocToMove.getName(), stepMoveBefore.getName());
670        }
671        if (DocumentRoutingWebConstants.MOVE_STEP_DOWN.equals(direction)) {
672            if (selectedDocumentIndex == orderedChilds.size() - 1) {
673                facesMessages.add(
674                        StatusMessage.Severity.WARN,
675                        resourcesAccessor.getMessages().get(
676                                "feedback.casemanagement.document.already.last.step.in.container"));
677                return null;
678            }
679            routeElementDocToMove.getAdapter(DocumentRouteElement.class);
680            DocumentModel stepMoveAfter = orderedChilds.get(selectedDocumentIndex + 1);
681            DocumentRouteElement stepElementMoveAfter = stepMoveAfter.getAdapter(DocumentRouteElement.class);
682            if (stepElementMoveAfter.isRunning()) {
683                facesMessages.add(
684                        StatusMessage.Severity.WARN,
685                        resourcesAccessor.getMessages().get(
686                                "feedback.casemanagement.document.route.cant.move.step.after.already.running.step"));
687                return null;
688            }
689            documentManager.orderBefore(parentDoc.getRef(), orderedChilds.get(selectedDocumentIndex + 1).getName(),
690                    routeElementDocToMove.getName());
691        }
692        if (docWithAttachedRouteId == null) {
693            return webActions.setCurrentTabAndNavigate(getRelatedRoute().getDocument(), "TAB_DOCUMENT_ROUTE_ELEMENTS");
694        }
695
696        setRelatedRouteWhenNavigateBackToCase();
697        return webActions.setCurrentTabAndNavigate(documentManager.getDocument(new IdRef(docWithAttachedRouteId)),
698                "TAB_CASE_MANAGEMENT_VIEW_RELATED_ROUTE");
699    }
700
701    public String saveRouteElement() {
702        boolean alreadyLockedByCurrentUser = false;
703        DocumentRoute routeModel = getRelatedRoute();
704        if (getDocumentRoutingService().isLockedByCurrentUser(routeModel, documentManager)) {
705            alreadyLockedByCurrentUser = true;
706        } else {
707            lockRoute(routeModel);
708        }
709
710        DocumentModel newDocument = navigationContext.getChangeableDocument();
711        // Document has already been created if it has an id.
712        // This will avoid creation of many documents if user hit create button
713        // too many times.
714        if (newDocument.getId() != null) {
715            log.debug("Document " + newDocument.getName() + " already created");
716            return navigationContext.navigateToDocument(newDocument, "after-create");
717        }
718        String parentDocumentPath = (String) newDocument.getContextData(CoreEventConstants.PARENT_PATH);
719        String sourceDocumentName = (String) newDocument.getContextData(SOURCE_DOC_NAME);
720        DocumentRef routeDocRef = (DocumentRef) newDocument.getContextData(ROUTE_DOCUMENT_REF);
721        try {
722            getDocumentRoutingService().addRouteElementToRoute(new PathRef(parentDocumentPath), sourceDocumentName,
723                    newDocument.getAdapter(DocumentRouteElement.class), documentManager);
724        } catch (DocumentRouteNotLockedException e) {
725            facesMessages.add(StatusMessage.Severity.WARN,
726                    resourcesAccessor.getMessages().get("feedback.casemanagement.document.route.not.locked"));
727            return null;
728        }
729        DocumentModel routeDocument = documentManager.getDocument(routeDocRef);
730        facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("document_saved"),
731                resourcesAccessor.getMessages().get(newDocument.getType()));
732
733        Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, routeDocument);
734        // Release the lock only when currentUser had locked it before
735        // entering this method.
736        if (!alreadyLockedByCurrentUser) {
737            getDocumentRoutingService().unlockDocumentRoute(routeModel, documentManager);
738        }
739        return navigationContext.navigateToDocument(routeDocument);
740    }
741
742    @Deprecated
743    // @deprecated since 5.9.2 - Use only routes of type 'graph'
744    public List<DocumentModel> getOrderedChildren(String docRouteElementId, String type) {
745        // xxx move me in serice with query
746        DocumentModelList orderedChildren = getDocumentRoutingService().getOrderedRouteElement(docRouteElementId,
747                documentManager);
748        List<DocumentModel> filteredChildren = new ArrayList<DocumentModel>();
749        for (DocumentModel documentModel : orderedChildren) {
750            if (type.equals(documentModel.getType())) {
751                filteredChildren.add(documentModel);
752            }
753        }
754        return filteredChildren;
755    }
756
757    @Deprecated
758    // @deprecated since 5.9.2 - Use only routes of type 'graph'
759    public DocumentModel getChildWithPosition(DocumentModel docRouteElement, String pos) {
760        DocumentModelList orderedChildren = getDocumentRoutingService().getOrderedRouteElement(docRouteElement.getId(),
761                documentManager);
762        return orderedChildren.get(Integer.parseInt(pos));
763    }
764
765    @Deprecated
766    // @deprecated since 5.9.2 - Use only routes of type 'graph'
767    public String getPositionForChild(DocumentModel docRouteElement, DocumentModel docChild) {
768        DocumentModelList orderedChildren = getDocumentRoutingService().getOrderedRouteElement(docRouteElement.getId(),
769                documentManager);
770        return String.valueOf(orderedChildren.indexOf(docChild));
771    }
772
773    public String getHiddenSourceDocId() {
774        return hiddenSourceDocId;
775    }
776
777    public void setHiddenSourceDocId(String hiddenSourceDocId) {
778        this.hiddenSourceDocId = hiddenSourceDocId;
779    }
780
781    public String getHiddenDocOrder() {
782        return hiddenDocOrder;
783    }
784
785    public void setHiddenDocOrder(String hiddenDocOrder) {
786        this.hiddenDocOrder = hiddenDocOrder;
787    }
788
789    public String getRelatedRouteModelDocumentId() {
790        return relatedRouteModelDocumentId;
791    }
792
793    public void setRelatedRouteModelDocumentId(String relatedRouteModelDocumentId) {
794        this.relatedRouteModelDocumentId = relatedRouteModelDocumentId;
795    }
796
797    public String getDocWithAttachedRouteId() {
798        return docWithAttachedRouteId;
799    }
800
801    public void setDocWithAttachedRouteId(String docWithAttachedRouteId) {
802        this.docWithAttachedRouteId = docWithAttachedRouteId;
803    }
804
805    private void setRelatedRouteWhenNavigateBackToCase() {
806        // recompute factory
807        webActions.resetTabList();
808        navigationContext.setCurrentDocument(documentManager.getDocument(new IdRef(docWithAttachedRouteId)));
809        relatedRoutes = relatedRouteAction.findRelatedRoute();
810    }
811
812    @Observer(value = { TaskEventNames.WORKFLOW_ENDED, TaskEventNames.WORKFLOW_CANCELED,
813            TaskEventNames.WORKFLOW_TASK_COMPLETED }, create = false)
814    public void resetCache() {
815        relatedRoutes = null;
816        if (!hasRelatedRoute()) {
817            webActions.resetTabList();
818        }
819    }
820
821    /**
822     * @since 5.6
823     */
824    public DocumentModel getRouteModel(String routeId) {
825        return documentManager.getDocument(new IdRef(routeId));
826    }
827
828    /**
829     * @since 5.6
830     */
831    public DocumentModel getRouteInstanceFor(Task task) {
832        final String routeDocId = task.getVariable(DocumentRoutingConstants.TASK_ROUTE_INSTANCE_DOCUMENT_ID_KEY);
833        if (routeDocId == null) {
834            return null;
835        }
836        final DocumentModel[] res = new DocumentModel[1];
837        new UnrestrictedSessionRunner(documentManager) {
838            @Override
839            public void run() {
840                DocumentModel doc = session.getDocument(new IdRef(routeDocId));
841                doc.detach(true);
842                res[0] = doc;
843            }
844        }.runUnrestricted();
845        return res[0];
846    }
847
848    /**
849     * @since 5.6
850     */
851    public List<DocumentModel> getFilteredRouteModels() {
852        DocumentRoutingService documentRoutingService = Framework.getService(DocumentRoutingService.class);
853        List<DocumentModel> routeModels = documentRoutingService.searchRouteModels(documentManager, "");
854        for (Iterator<DocumentModel> it = routeModels.iterator(); it.hasNext();) {
855            DocumentModel route = it.next();
856            Object graphRouteObj = route.getAdapter(GraphRoute.class);
857            if (graphRouteObj instanceof GraphRoute) {
858                String filter = ((GraphRoute) graphRouteObj).getAvailabilityFilter();
859                if (!StringUtils.isBlank(filter)) {
860                    if (!webActions.checkFilter(filter)) {
861                        it.remove();
862                    }
863                }
864            } else {
865                // old workflow document => ignore
866                it.remove();
867            }
868        }
869        return routeModels;
870    }
871
872    /**
873     * @since 5.6
874     */
875    public List<Task> getCurrentRouteAllTasks() {
876        TaskService taskService = Framework.getService(TaskService.class);
877        DocumentRoute currentRoute = getRelatedRoute();
878        if (currentRoute != null) {
879            return taskService.getAllTaskInstances(currentRoute.getDocument().getId(), documentManager);
880        }
881        return null;
882    }
883
884    /**
885     * @since 5.6
886     */
887    public List<Task> getCurrentRouteCurrentUserTasks() {
888        TaskService taskService = Framework.getService(TaskService.class);
889        DocumentRoute currentRoute = getRelatedRoute();
890        if (currentRoute != null) {
891            return taskService.getAllTaskInstances(currentRoute.getDocument().getId(), documentManager.getPrincipal(),
892                    documentManager);
893        }
894        return null;
895    }
896
897    /**
898     * @since 5.6
899     */
900    public String getCurrentWorkflowInitiator() {
901        DocumentRoute currentRoute = getRelatedRoute();
902        if (currentRoute != null) {
903            return (String) currentRoute.getDocument().getPropertyValue(DocumentRoutingConstants.INITIATOR);
904        }
905        return "";
906    }
907
908    /**
909     * since 5.7
910     */
911    public boolean isCurrentRouteGraph() {
912        return isRouteGraph(getRelatedRoute());
913    }
914
915    /**
916     * Checks if a given route is a Graph.
917     *
918     * @since 5.7.2
919     */
920    public boolean isRouteGraph(DocumentRoute route) {
921        return route != null
922                && ExecutionTypeValues.graph.toString().equals(
923                        route.getDocument().getPropertyValue(DocumentRoutingConstants.EXECUTION_TYPE_PROPERTY_NAME));
924    }
925
926}