001/*
002 * (C) Copyright 2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *    Mariana Cedica
016 *
017 * $Id$
018 */
019package org.nuxeo.ecm.platform.routing.web;
020
021import static org.jboss.seam.ScopeType.CONVERSATION;
022import static org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager.CURRENT_DOCUMENT_SELECTION;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030
031import javax.faces.convert.Converter;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.jboss.seam.ScopeType;
037import org.jboss.seam.annotations.Factory;
038import org.jboss.seam.annotations.In;
039import org.jboss.seam.annotations.Install;
040import org.jboss.seam.annotations.Name;
041import org.jboss.seam.annotations.Observer;
042import org.jboss.seam.annotations.Scope;
043import org.jboss.seam.annotations.web.RequestParameter;
044import org.jboss.seam.contexts.Contexts;
045import org.jboss.seam.core.Events;
046import org.jboss.seam.faces.FacesMessages;
047import org.jboss.seam.international.StatusMessage;
048import org.nuxeo.common.collections.ScopedMap;
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.getLocalService(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        ScopedMap context = changeableDocument.getContextData();
613        context.put(CoreEventConstants.PARENT_PATH, parentPath);
614        context.put(SOURCE_DOC_NAME, sourceDocName);
615        context.put(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().get(CoreEventConstants.PARENT_PATH);
720        String sourceDocumentName = (String) newDocument.getContextData().get(SOURCE_DOC_NAME);
721        DocumentRef routeDocRef = (DocumentRef) newDocument.getContextData().get(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}