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