001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
016 *
017 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
018 */
019
020package org.nuxeo.ecm.webapp.context;
021
022import static org.jboss.seam.ScopeType.CONVERSATION;
023import static org.jboss.seam.ScopeType.EVENT;
024import static org.jboss.seam.annotations.Install.FRAMEWORK;
025import static org.nuxeo.ecm.webapp.helpers.EventNames.NAVIGATE_TO_DOCUMENT;
026
027import java.io.Serializable;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.jboss.seam.Component;
034import org.jboss.seam.annotations.Begin;
035import org.jboss.seam.annotations.Create;
036import org.jboss.seam.annotations.Factory;
037import org.jboss.seam.annotations.In;
038import org.jboss.seam.annotations.Install;
039import org.jboss.seam.annotations.Name;
040import org.jboss.seam.annotations.Scope;
041import org.jboss.seam.annotations.intercept.BypassInterceptors;
042import org.jboss.seam.annotations.web.RequestParameter;
043import org.jboss.seam.contexts.Context;
044import org.jboss.seam.contexts.Contexts;
045import org.jboss.seam.core.Events;
046import org.nuxeo.common.utils.Path;
047import org.nuxeo.ecm.core.api.CoreSession;
048import org.nuxeo.ecm.core.api.DocumentLocation;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.core.api.DocumentModelList;
051import org.nuxeo.ecm.core.api.DocumentRef;
052import org.nuxeo.ecm.core.api.IdRef;
053import org.nuxeo.ecm.core.api.NuxeoException;
054import org.nuxeo.ecm.core.api.VersionModel;
055import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
056import org.nuxeo.ecm.core.api.security.SecurityConstants;
057import org.nuxeo.ecm.core.schema.FacetNames;
058import org.nuxeo.ecm.core.schema.SchemaManager;
059import org.nuxeo.ecm.platform.types.Type;
060import org.nuxeo.ecm.platform.types.adapter.TypeInfo;
061import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
062import org.nuxeo.ecm.platform.ui.web.api.UserAction;
063import org.nuxeo.ecm.platform.ui.web.pathelements.ArchivedVersionsPathElement;
064import org.nuxeo.ecm.platform.ui.web.pathelements.DocumentPathElement;
065import org.nuxeo.ecm.platform.ui.web.pathelements.HiddenDocumentPathElement;
066import org.nuxeo.ecm.platform.ui.web.pathelements.PathElement;
067import org.nuxeo.ecm.platform.ui.web.pathelements.VersionDocumentPathElement;
068import org.nuxeo.ecm.platform.ui.web.util.BadDocumentUriException;
069import org.nuxeo.ecm.platform.ui.web.util.DocumentLocator;
070import org.nuxeo.ecm.platform.ui.web.util.DocumentsListsUtils;
071import org.nuxeo.ecm.platform.util.RepositoryLocation;
072import org.nuxeo.ecm.webapp.action.TypesTool;
073import org.nuxeo.ecm.webapp.delegate.DocumentManagerBusinessDelegate;
074import org.nuxeo.ecm.webapp.helpers.ApplicationControllerHelper;
075import org.nuxeo.ecm.webapp.helpers.EventManager;
076import org.nuxeo.ecm.webapp.helpers.EventNames;
077import org.nuxeo.runtime.api.Framework;
078
079/**
080 * Implementation for the navigationContext component available on the session.
081 */
082@Name("navigationContext")
083@Scope(CONVERSATION)
084@Install(precedence = FRAMEWORK)
085public class NavigationContextBean implements NavigationContext, Serializable {
086
087    private static final long serialVersionUID = -3708768859028774906L;
088
089    private static final Log log = LogFactory.getLog(NavigationContextBean.class);
090
091    // --------------------------------------------
092    // fields managed by this class
093    // These fields can be accessed by 2 ways
094    // - simple getters
095    // - via the context thanks to @Factory
096
097    private DocumentModel currentDomain;
098
099    private DocumentModel currentContentRoot;
100
101    private DocumentModel currentWorkspace;
102
103    protected DocumentModel currentDocument;
104
105    protected DocumentModel currentSuperSpace;
106
107    protected DocumentModelList currentDocumentChildren;
108
109    protected List<DocumentModel> currentDocumentParents;
110
111    // document model that is not persisted yet (used for creation)
112    private DocumentModel changeableDocument;
113
114    private List<PathElement> parents;
115
116    private SchemaManager schemaManager;
117
118    @In(create = true, required = false)
119    protected transient CoreSession documentManager;
120
121    @Override
122    @Create
123    public void init() {
124        parents = null;
125    }
126
127    @Override
128    @BypassInterceptors
129    public DocumentModel getCurrentDocument() {
130        return currentDocument;
131    }
132
133    @Override
134    public String getCurrentDomainPath() {
135        if (currentDomain != null) {
136            return currentDomain.getPathAsString();
137        }
138        Path path;
139        if (currentDocument != null) {
140            path = currentDocument.getPath();
141        } else {
142            // Find any document, and lookup its domain.
143            DocumentModelList docs = documentManager.query("SELECT * FROM Document", 1);
144            if (docs.size() < 1) {
145                log.debug("Could not find a single document readable by current user.");
146                return null;
147            }
148            path = docs.get(0).getPath();
149        }
150        if (path.segmentCount() > 0) {
151            String[] segs = { path.segment(0) };
152            return Path.createFromSegments(segs).toString();
153        } else {
154            return null;
155        }
156    }
157
158    @Override
159    public void setCurrentDocument(DocumentModel documentModel) {
160        if (log.isDebugEnabled()) {
161            log.debug("Setting current document to " + documentModel);
162        }
163
164        if (!checkIfUpdateNeeded(currentDocument, documentModel)) {
165            if (log.isDebugEnabled()) {
166                log.debug(String.format("Current document already set to %s => give up updates", documentModel));
167            }
168            return;
169        }
170
171        currentSuperSpace = null;
172        currentDocument = documentModel;
173        // update all depending variables
174        updateContextVariables();
175        resetCurrentPath();
176        Contexts.getEventContext().remove("currentDocument");
177
178        EventManager.raiseEventsOnDocumentSelected(currentDocument);
179
180        if (log.isDebugEnabled()) {
181            log.debug("Current document set to: " + changeableDocument);
182        }
183    }
184
185    @Override
186    @BypassInterceptors
187    public DocumentModel getChangeableDocument() {
188        return changeableDocument;
189    }
190
191    @Override
192    public void setChangeableDocument(DocumentModel changeableDocument) {
193        if (log.isDebugEnabled()) {
194            log.debug("Setting changeable document to: " + changeableDocument);
195        }
196        this.changeableDocument = changeableDocument;
197        Contexts.getEventContext().set("changeableDocument", changeableDocument);
198    }
199
200    @Override
201    public DocumentModelList getCurrentPath() {
202        DocumentModelList parentDocsList = new DocumentModelListImpl();
203
204        List<DocumentModel> fromRoot = documentManager.getParentDocuments(currentDocument.getRef());
205        // add in reverse order
206        parentDocsList.addAll(fromRoot);
207        Collections.reverse(parentDocsList);
208
209        return parentDocsList;
210    }
211
212    @Override
213    public DocumentModel getCurrentSuperSpace() {
214        if (currentSuperSpace == null && currentDocument != null) {
215            if (currentDocument.hasFacet(FacetNames.SUPER_SPACE)) {
216                currentSuperSpace = currentDocument;
217            } else if (documentManager != null) {
218                currentSuperSpace = documentManager.getSuperSpace(currentDocument);
219            }
220        }
221        return currentSuperSpace;
222    }
223
224    @Override
225    public void invalidateCurrentDocument() {
226        if (currentDocument != null) {
227            currentDocument = documentManager.getDocument(currentDocument.getRef());
228            updateContextVariables();
229        }
230    }
231
232    @Override
233    @BypassInterceptors
234    public DocumentModel getCurrentDomain() {
235        return currentDomain;
236    }
237
238    @Override
239    public void setCurrentDomain(DocumentModel domainDocModel) {
240        if (!checkIfUpdateNeeded(currentDomain, domainDocModel)) {
241            return;
242        }
243
244        currentDomain = domainDocModel;
245        Contexts.getEventContext().remove("currentDomain");
246
247        if (domainDocModel == null) {
248            Events.instance().raiseEvent(EventNames.DOMAIN_SELECTION_CHANGED, currentDomain);
249            return;
250        }
251
252        if (currentDocument == null) {
253            setCurrentDocument(null);
254        }
255
256        // if we switched branch then realign currentDocument
257        if (currentDocumentParents != null
258                && !DocumentsListsUtils.isDocumentInList(domainDocModel, currentDocumentParents)) {
259            setCurrentDocument(domainDocModel);
260        }
261
262        Events.instance().raiseEvent(EventNames.DOMAIN_SELECTION_CHANGED, currentDomain);
263    }
264
265    protected boolean checkIfUpdateNeeded(DocumentModel ctxDoc, DocumentModel newDoc) {
266        if (log.isDebugEnabled()) {
267            log.debug(String.format("Check if update needed: compare context " + "doc '%s' to new doc '%s'", ctxDoc,
268                    newDoc));
269        }
270        if (ctxDoc == null && newDoc != null || ctxDoc != null && newDoc == null) {
271            return true;
272        }
273        if (ctxDoc == null && newDoc == null) {
274            return false;
275        }
276        if (log.isDebugEnabled()) {
277            log.debug(String.format(
278                    "Check if update needed: compare cache key on " + "context doc '%s' with new doc '%s'",
279                    ctxDoc.getCacheKey(), newDoc.getCacheKey()));
280        }
281        return !ctxDoc.getCacheKey().equals(newDoc.getCacheKey());
282    }
283
284    @Override
285    public void saveCurrentDocument() {
286        if (currentDocument == null) {
287            // cannot call saveDocument with null arg => nasty stateful bean
288            // de-serialization error
289            throw new IllegalStateException("null currentDocument");
290        }
291        currentDocument = documentManager.saveDocument(currentDocument);
292        documentManager.save();
293    }
294
295    @Override
296    public List<PathElement> getCurrentPathList() {
297        if (parents == null) {
298            resetCurrentPath();
299        }
300        return parents;
301    }
302
303    protected ServerContextBean getServerLocator() {
304        return (ServerContextBean) Component.getInstance("serverLocator");
305    }
306
307    @Override
308    public RepositoryLocation getCurrentServerLocation() {
309        return getServerLocator().getCurrentServerLocation();
310    }
311
312    /**
313     * @deprecated use getCurrentServerLocation() instead
314     */
315    @Override
316    @Deprecated
317    public RepositoryLocation getSelectedServerLocation() {
318        return getServerLocator().getCurrentServerLocation();
319    }
320
321    /**
322     * Switches to a new server location by updating the context and updating to the CoreSession (DocumentManager).
323     */
324    @Override
325    public void setCurrentServerLocation(RepositoryLocation serverLocation) {
326        if (serverLocation == null) {
327            log.warn("Setting ServerLocation to null, is this normal ?");
328        }
329
330        RepositoryLocation currentServerLocation = serverLocation;
331        getServerLocator().setRepositoryLocation(serverLocation);
332        resetCurrentContext();
333        Contexts.getEventContext().set("currentServerLocation", currentServerLocation);
334
335        // update the documentManager
336        documentManager = null;
337        documentManager = getOrCreateDocumentManager();
338        Events.instance().raiseEvent(EventNames.LOCATION_SELECTION_CHANGED);
339
340        DocumentModel rootDocument = documentManager.getRootDocument();
341        if (documentManager.hasPermission(rootDocument.getRef(), SecurityConstants.READ)) {
342            currentDocument = rootDocument;
343            updateContextVariables();
344        }
345    }
346
347    /**
348     * Returns the current documentManager if any or create a new session to the current location.
349     */
350    @Override
351    public CoreSession getOrCreateDocumentManager() {
352        if (documentManager != null) {
353            return documentManager;
354        }
355        // protect for unexpected wrong cast
356        Object supposedDocumentManager = Contexts.lookupInStatefulContexts("documentManager");
357        DocumentManagerBusinessDelegate documentManagerBD = null;
358        if (supposedDocumentManager != null) {
359            if (supposedDocumentManager instanceof DocumentManagerBusinessDelegate) {
360                documentManagerBD = (DocumentManagerBusinessDelegate) supposedDocumentManager;
361            } else {
362                log.error("Found the documentManager being " + supposedDocumentManager.getClass()
363                        + " instead of DocumentManagerBusinessDelegate. This is wrong.");
364            }
365        }
366        if (documentManagerBD == null) {
367            // this is the first time we select the location, create a
368            // DocumentManagerBusinessDelegate instance
369            documentManagerBD = new DocumentManagerBusinessDelegate();
370            Contexts.getConversationContext().set("documentManager", documentManagerBD);
371        }
372        documentManager = documentManagerBD.getDocumentManager(getCurrentServerLocation());
373        return documentManager;
374    }
375
376    @Override
377    @BypassInterceptors
378    public DocumentModel getCurrentWorkspace() {
379        return currentWorkspace;
380    }
381
382    // Factories to make navigation related data
383    // available in the context
384
385    @Override
386    @Factory(value = "currentDocument", scope = EVENT)
387    public DocumentModel factoryCurrentDocument() {
388        return currentDocument;
389    }
390
391    @Override
392    @Factory(value = "changeableDocument", scope = EVENT)
393    public DocumentModel factoryChangeableDocument() {
394        return changeableDocument;
395    }
396
397    @Override
398    @Factory(value = "currentDomain", scope = EVENT)
399    public DocumentModel factoryCurrentDomain() {
400        return currentDomain;
401    }
402
403    @Override
404    @Factory(value = "currentWorkspace", scope = EVENT)
405    public DocumentModel factoryCurrentWorkspace() {
406        return currentWorkspace;
407    }
408
409    @Override
410    @Factory(value = "currentContentRoot", scope = EVENT)
411    public DocumentModel factoryCurrentContentRoot() {
412        return currentContentRoot;
413    }
414
415    // @Factory(value = "currentServerLocation", scope = EVENT)
416    @Override
417    public RepositoryLocation factoryCurrentServerLocation() {
418        return getCurrentServerLocation();
419    }
420
421    @Override
422    @Factory(value = "currentSuperSpace", scope = EVENT)
423    public DocumentModel factoryCurrentSuperSpace() {
424        return getCurrentSuperSpace();
425    }
426
427    public void setCurrentWorkspace(DocumentModel workspaceDocModel) {
428
429        if (!checkIfUpdateNeeded(currentWorkspace, workspaceDocModel)) {
430            return;
431        }
432        currentWorkspace = workspaceDocModel;
433
434        if (workspaceDocModel == null) {
435            return;
436        }
437
438        if (currentDocument == null) {
439            setCurrentDocument(workspaceDocModel);
440            return;
441        }
442
443        // if we switched branch then realign currentDocument
444        if (currentDocumentParents != null
445                && !DocumentsListsUtils.isDocumentInList(workspaceDocModel, currentDocumentParents)) {
446            setCurrentDocument(workspaceDocModel);
447            return;
448        }
449    }
450
451    @Override
452    public void updateDocumentContext(DocumentModel doc) {
453        setCurrentDocument(doc);
454    }
455
456    /**
457     * Updates variables according to hierarchy rules and to the new currentDocument.
458     */
459    protected void updateContextVariables() {
460
461        // XXX flush method is not implemented for Event context :)
462        Contexts.getEventContext().set("currentDocument", currentDocument);
463
464        // Don't flush changeable document with a null id (NXP-10732)
465        if ((getChangeableDocument() != null) && (getChangeableDocument().getId() != null)) {
466            setChangeableDocument(null);
467        }
468
469        if (currentDocument == null) {
470            currentDocumentParents = null;
471            return;
472        }
473
474        DocumentRef ref = currentDocument.getRef();
475        if (ref == null) {
476            throw new NuxeoException("DocumentRef is null for currentDocument: " + currentDocument.getName());
477        }
478        // Recompute document parents
479        currentDocumentParents = documentManager.getParentDocuments(ref);
480
481        // iterate in reverse list order to go down the tree
482        // set all navigation variables according to docType
483        // => update to tree
484        String docType;
485        if (currentDocumentParents != null) {
486            for (int i = currentDocumentParents.size() - 1; i >= 0; i--) {
487                DocumentModel docModel = currentDocumentParents.get(i);
488                docType = docModel.getType();
489
490                if (docType != null && hasSuperType(docType, "Workspace")) {
491                    setCurrentWorkspace(docModel);
492                }
493
494                if (docType == null || hasSuperType(docType, "WorkspaceRoot") || hasSuperType(docType, "SectionRoot")) {
495                    setCurrentContentRoot(docModel);
496                }
497
498                if (docType != null && hasSuperType(docType, "Domain")) {
499                    setCurrentDomain(docModel);
500                }
501            }
502        }
503
504        // reinit lower tree
505        docType = currentDocument.getType();
506        if (docType.equals("Root")) {
507            setCurrentDomain(null);
508            setCurrentContentRoot(null);
509            setCurrentWorkspace(null);
510        } else if (hasSuperType(docType, "Domain")) {
511            setCurrentDomain(currentDocument);
512            setCurrentContentRoot(null);
513            setCurrentWorkspace(null);
514        } else if (hasSuperType(docType, "WorkspaceRoot") || hasSuperType(docType, "SectionRoot")) {
515            setCurrentContentRoot(currentDocument);
516            setCurrentWorkspace(null);
517        } else if (hasSuperType(docType, "Workspace")) {
518            setCurrentWorkspace(currentDocument);
519        }
520
521        // lazily recompute some fields
522        currentSuperSpace = null;
523        parents = null;
524    }
525
526    private boolean hasSuperType(String targetDocType, String superType) {
527        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
528        return schemaManager.hasSuperType(targetDocType, superType);
529    }
530
531    @Override
532    public void resetCurrentContext() {
533        // flush event context
534        Context eventContext = Contexts.getEventContext();
535        eventContext.remove("currentDocument");
536        eventContext.remove("changeableDocument");
537        eventContext.remove("currentDocumentChildren");
538        eventContext.remove("currentDomain");
539        eventContext.remove("currentServerLocation");
540        eventContext.remove("currentWorkspace");
541        eventContext.remove("currentContentRoot");
542        eventContext.remove("currentSuperSpace");
543    }
544
545    // XXX AT: we should let each action listener raise specific events
546    // (edition) and decide what's the next view, let's just handle context
547    // setting and redirection + this should be callable from templates i.e use
548    // view as a string.
549    @Override
550    public String getActionResult(DocumentModel doc, UserAction action) {
551
552        TypesTool typesTool = (TypesTool) Component.getInstance("typesTool");
553
554        if (doc == null) {
555            return null;
556        }
557        if (UserAction.CREATE == action) {
558            // the given document is a changeable document
559            setChangeableDocument(doc);
560        } else {
561            updateDocumentContext(doc);
562        }
563
564        final Type type = typesTool.getType(doc.getType());
565
566        final String result;
567        if (UserAction.VIEW == action) {
568            assert currentDocument != null;
569            EventManager.raiseEventsOnDocumentSelected(currentDocument);
570            result = ApplicationControllerHelper.getPageOnSelectedDocumentType(type);
571        } else if (UserAction.EDIT == action) {
572            throw new UnsupportedOperationException("for action " + action);
573        } else if (UserAction.AFTER_EDIT == action) {
574            assert currentDocument != null;
575            EventManager.raiseEventsOnDocumentChange(currentDocument);
576            result = ApplicationControllerHelper.getPageOnEditedDocumentType(type);
577        } else if (UserAction.CREATE == action) {
578            EventManager.raiseEventsOnDocumentCreate(changeableDocument);
579            result = ApplicationControllerHelper.getPageOnCreateDocumentType(type);
580        } else if (UserAction.AFTER_CREATE == action) {
581            assert currentDocument != null;
582            EventManager.raiseEventsOnDocumentSelected(currentDocument);
583            result = ApplicationControllerHelper.getPageOnCreatedDocumentType(type);
584        } else if (UserAction.GO_HOME == action) {
585            EventManager.raiseEventsOnGoingHome();
586            result = "home";
587        } else {
588            log.error(String.format("Unknown action '%s' for navigation on " + "document '%s' with title '%s': ",
589                    action.name(), doc.getId(), doc.getTitle()));
590            result = null;
591        }
592        return result;
593    }
594
595    @Override
596    public String goHome() {
597        resetCurrentContext();
598        EventManager.raiseEventsOnGoingHome();
599        return "home";
600    }
601
602    @Override
603    public String goBack() {
604        if (currentDocument != null) {
605            setChangeableDocument(null);
606            return navigateToDocument(currentDocument);
607        } else {
608            return goHome();
609        }
610    }
611
612    @Override
613    public String navigateToId(String documentId) {
614        if (documentManager == null) {
615            throw new IllegalStateException("documentManager not initialized");
616        }
617        DocumentRef docRef = new IdRef(documentId);
618        final DocumentModel doc = documentManager.getDocument(docRef);
619        return navigateToDocument(doc, "view");
620    }
621
622    @Override
623    public String navigateToRef(DocumentRef docRef) {
624        if (documentManager == null) {
625            throw new IllegalStateException("documentManager not initialized");
626        }
627
628        final DocumentModel doc = documentManager.getDocument(docRef);
629
630        return navigateToDocument(doc, "view");
631    }
632
633    @Override
634    public String navigateToDocument(DocumentModel doc) {
635        return navigateToDocument(doc, "view");
636    }
637
638    @Override
639    public String navigateToDocument(DocumentModel doc, String viewId) {
640        if (doc != null) {
641            updateDocumentContext(doc);
642        }
643        assert currentDocument != null;
644        TypeInfo typeInfo = currentDocument.getAdapter(TypeInfo.class);
645        String chosenView = null;
646        if (typeInfo != null) {
647            String defaultView = typeInfo.getDefaultView();
648            // hardcoded default views
649            if ("view".equals(viewId)) {
650                chosenView = defaultView;
651            } else if ("create".equals(viewId)) {
652                chosenView = typeInfo.getCreateView();
653            } else if ("edit".equals(viewId)) {
654                chosenView = typeInfo.getEditView();
655            } else {
656                chosenView = typeInfo.getView(viewId);
657            }
658            if (chosenView == null) {
659                chosenView = defaultView;
660            }
661        }
662
663        Events.instance().raiseEvent(NAVIGATE_TO_DOCUMENT, currentDocument);
664
665        return chosenView;
666    }
667
668    @Override
669    public String navigateToDocumentWithView(DocumentModel doc, String viewId) {
670        return navigateToDocument(doc, viewId);
671    }
672
673    @Override
674    public String navigateToDocument(DocumentModel docModel, VersionModel versionModel) {
675        DocumentModel docVersion = documentManager.getDocumentWithVersion(docModel.getRef(), versionModel);
676        return navigateToDocument(docVersion);
677    }
678
679    @Override
680    public void selectionChanged() {
681        resetCurrentPath();
682    }
683
684    @Override
685    public String getCurrentDocumentUrl() {
686        if (currentDocument == null) {
687            log.error("current document is null");
688            return null;
689        }
690        return DocumentLocator.getDocumentUrl(getCurrentServerLocation(), currentDocument.getRef());
691    }
692
693    @Override
694    public String getCurrentDocumentFullUrl() {
695        if (currentDocument == null) {
696            log.error("current document is null");
697            return null;
698        }
699        return DocumentLocator.getFullDocumentUrl(getCurrentServerLocation(), currentDocument.getRef());
700    }
701
702    // start a new conversation if needed, join main if possible
703    @Override
704    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
705    public String navigateTo(RepositoryLocation serverLocation, DocumentRef docRef) {
706        // re-connect only if there is another repository specified
707        if (!serverLocation.equals(getCurrentServerLocation())) {
708            setCurrentServerLocation(serverLocation);
709        }
710        return navigateToRef(docRef);
711    }
712
713    // start a new conversation if needed, join main if possible
714    @Override
715    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
716    public String navigateToURL(String documentUrl) {
717        final DocumentLocation docLoc;
718        try {
719            docLoc = DocumentLocator.parseDocRef(documentUrl);
720        } catch (BadDocumentUriException e) {
721            log.error("Cannot get document ref from uri " + documentUrl + ". " + e.getMessage(), e);
722            return null;
723        }
724        final DocumentRef docRef = docLoc.getDocRef();
725        RepositoryLocation repLoc = new RepositoryLocation(docLoc.getServerName());
726        return navigateTo(repLoc, docRef);
727    }
728
729    @RequestParameter
730    String docRef;
731
732    /**
733     * @see NavigationContext#navigateToURL()
734     */
735    @Override
736    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
737    public String navigateToURL() {
738        if (docRef == null) {
739            return null;
740        }
741        return navigateToURL(docRef);
742    }
743
744    protected void resetCurrentPath() {
745        final String logPrefix = "<resetCurrentPath> ";
746
747        parents = new ArrayList<PathElement>();
748
749        if (documentManager == null) {
750            log.error(logPrefix + "documentManager not initialized");
751            return;
752        }
753
754        if (currentDocument != null) {
755            if (currentDocument.isVersion()) {
756                DocumentModel sourceDocument = documentManager.getSourceDocument(currentDocument.getRef());
757
758                List<DocumentModel> parentList = documentManager.getParentDocuments(sourceDocument.getRef());
759                for (DocumentModel docModel : parentList) {
760                    parents.add(getDocumentPathElement(docModel));
761                }
762
763                parents.add(new ArchivedVersionsPathElement(sourceDocument));
764                parents.add(new VersionDocumentPathElement(currentDocument));
765            } else {
766                if (currentDocumentParents != null) {
767                    for (DocumentModel docModel : currentDocumentParents) {
768                        parents.add(getDocumentPathElement(docModel));
769                    }
770                }
771            }
772        }
773    }
774
775    protected PathElement getDocumentPathElement(DocumentModel doc) {
776        if (doc != null && doc.hasFacet(FacetNames.HIDDEN_IN_NAVIGATION)) {
777            return new HiddenDocumentPathElement(doc);
778        }
779        return new DocumentPathElement(doc);
780    }
781
782    @Override
783    public DocumentModel getCurrentContentRoot() {
784        return currentContentRoot;
785    }
786
787    @Override
788    public void setCurrentContentRoot(DocumentModel crDocumentModel) {
789
790        if (!checkIfUpdateNeeded(currentContentRoot, crDocumentModel)) {
791            return;
792        }
793        currentContentRoot = crDocumentModel;
794        Contexts.getEventContext().remove("currentContentRoot");
795
796        if (crDocumentModel == null) {
797            return;
798        }
799
800        if (currentDocument == null) {
801            setCurrentDocument(null);
802            return;
803        }
804
805        // if we switched branch then realign currentDocument
806        if (currentDocumentParents != null
807                && !DocumentsListsUtils.isDocumentInList(crDocumentModel, currentDocumentParents)) {
808            setCurrentDocument(crDocumentModel);
809        }
810    }
811
812}