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