/* * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Nuxeo - initial API and implementation * * $Id$ */ package org.nuxeo.ecm.webapp.tree; import static org.jboss.seam.ScopeType.CONVERSATION; import static org.jboss.seam.annotations.Install.FRAMEWORK; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.Component; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.nuxeo.common.utils.Path; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.Filter; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.Sorter; import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.runtime.api.Framework; import org.richfaces.event.CollapsibleSubTableToggleEvent; /** * Manages the navigation tree. * * @author Razvan Caraghin * @author Anahide Tchertchian */ @Scope(CONVERSATION) @Name("treeActions") @Install(precedence = FRAMEWORK) public class TreeActionsBean implements TreeActions, Serializable { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(TreeActionsBean.class); public static final String NODE_SELECTED_MARKER = TreeActionsBean.class.getName() + "_NODE_SELECTED_MARKER"; @In(create = true, required = false) protected transient CoreSession documentManager; @In(create = true) protected transient NavigationContext navigationContext; protected Map<String, List<DocumentTreeNode>> trees = new HashMap<String, List<DocumentTreeNode>>(); protected String currentDocumentPath; @In(create = true, required = false) protected Boolean isUserWorkspace; @In(create = true, required = false) protected String currentPersonalWorkspacePath; protected String userWorkspacePath; // cache the path of the tree root to check if invalidation are needed when // bypassing interceptors protected String firstAccessibleParentPath; @In(create = true) protected TreeInvalidatorBean treeInvalidator; public List<DocumentTreeNode> getTreeRoots() throws ClientException { return getTreeRoots(false); } public List<DocumentTreeNode> getTreeRoots(String treeName) throws ClientException { return getTreeRoots(false, treeName); } protected List<DocumentTreeNode> getTreeRoots(boolean showRoot, String treeName) throws ClientException { return getTreeRoots(showRoot, navigationContext.getCurrentDocument(), treeName); } protected List<DocumentTreeNode> getTreeRoots(boolean showRoot) throws ClientException { return getTreeRoots(showRoot, navigationContext.getCurrentDocument(), DEFAULT_TREE_PLUGIN_NAME); } protected List<DocumentTreeNode> getTreeRoots(boolean showRoot, DocumentModel currentDocument) throws ClientException { return getTreeRoots(showRoot, currentDocument, DEFAULT_TREE_PLUGIN_NAME); } /** * @since 5.4 */ protected List<DocumentTreeNode> getTreeRoots(boolean showRoot, DocumentModel currentDocument, String treeName) throws ClientException { if (treeInvalidator.needsInvalidation()) { reset(); treeInvalidator.invalidationDone(); } if (Boolean.TRUE.equals(isUserWorkspace)) { userWorkspacePath = getUserWorkspacePath(); } List<DocumentTreeNode> currentTree = trees.get(treeName); if (currentTree == null) { currentTree = new ArrayList<DocumentTreeNode>(); DocumentModel firstAccessibleParent = null; if (currentDocument != null) { if (Boolean.TRUE.equals(isUserWorkspace)) { firstAccessibleParent = documentManager.getDocument(new PathRef( userWorkspacePath)); } else { List<DocumentModel> parents = documentManager.getParentDocuments(currentDocument.getRef()); if (!parents.isEmpty()) { firstAccessibleParent = parents.get(0); } else if (!"Root".equals(currentDocument.getType()) && currentDocument.isFolder()) { // default on current doc firstAccessibleParent = currentDocument; } else { if (showRoot) { firstAccessibleParent = currentDocument; } } } } firstAccessibleParentPath = firstAccessibleParent == null ? null : firstAccessibleParent.getPathAsString(); if (firstAccessibleParent != null) { Filter filter = null; Filter leafFilter = null; Sorter sorter = null; String pageProvider = null; try { TreeManager treeManager = Framework.getService(TreeManager.class); filter = treeManager.getFilter(treeName); leafFilter = treeManager.getLeafFilter(treeName); sorter = treeManager.getSorter(treeName); pageProvider = treeManager.getPageProviderName(treeName); } catch (Exception e) { log.error("Could not fetch filter or sorter for tree ", e); } DocumentTreeNode treeRoot = null; treeRoot = new DocumentTreeNodeImpl(firstAccessibleParent, filter, leafFilter, sorter, pageProvider); currentTree.add(treeRoot); log.debug("Tree initialized with document: " + firstAccessibleParent.getId()); } else { log.debug("Could not initialize the navigation tree: no parent" + " found for current document"); } trees.put(treeName, currentTree); } return trees.get(treeName); } @Deprecated public void changeExpandListener(CollapsibleSubTableToggleEvent event) { FacesContext facesContext = FacesContext.getCurrentInstance(); Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap(); requestMap.put(NODE_SELECTED_MARKER, Boolean.TRUE); } public String getCurrentDocumentPath() { if (currentDocumentPath == null) { DocumentModel currentDoc = navigationContext.getCurrentDocument(); if (currentDoc != null) { currentDocumentPath = currentDoc.getPathAsString(); } } return currentDocumentPath; } protected String getUserWorkspacePath() { String currentDocumentPath = getCurrentDocumentPath(); if (StringUtils.isBlank(currentPersonalWorkspacePath)) { reset(); return currentDocumentPath; } if (userWorkspacePath == null || !userWorkspacePath.contains(currentPersonalWorkspacePath)) { // navigate to another personal workspace reset(); try { return documentManager.exists(new PathRef( currentPersonalWorkspacePath)) ? currentPersonalWorkspacePath : findFarthestContainerPath(currentDocumentPath); } catch (ClientException e) { return currentDocumentPath; } } return userWorkspacePath; } protected String findFarthestContainerPath(String documentPath) throws ClientException { Path containerPath = new Path(documentPath); String result; do { result = containerPath.toString(); containerPath = containerPath.removeLastSegments(1); } while (!containerPath.isRoot() && documentManager.exists(new PathRef(containerPath.toString()))); return result; } @Observer(value = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED }, create = false) @BypassInterceptors public void resetCurrentDocumentData() { currentDocumentPath = null; if (checkIfTreeInvalidationNeeded()) { trees.clear(); return; } // reset tree in case an accessible parent is finally found this time // for the new current document for (List<DocumentTreeNode> tree : trees.values()) { if (tree != null && tree.isEmpty()) { tree = null; } } } protected boolean checkIfTreeInvalidationNeeded() { // NXP-9813: this check may consume more resource, because called each // time a document selection is changed but it guarantees a better // detection if moving from one tree to another without using // UserWorkspace actions from user menu, which raise appropriate events DocumentModel currentDocument = (DocumentModel) Component.getInstance("currentDocument"); if (currentDocument != null && firstAccessibleParentPath != null && currentDocument.getPathAsString() != null && (!currentDocument.getPathAsString().contains( firstAccessibleParentPath) || (userWorkspacePath != null && currentDocument.getPathAsString().contains( userWorkspacePath) && !firstAccessibleParentPath.contains(userWorkspacePath)))) { return true; } return false; } @Observer(value = { EventNames.GO_HOME, EventNames.DOMAIN_SELECTION_CHANGED, EventNames.DOCUMENT_CHANGED, EventNames.DOCUMENT_SECURITY_CHANGED, EventNames.DOCUMENT_CHILDREN_CHANGED }, create = false) @BypassInterceptors public void reset() { trees.clear(); resetCurrentDocumentData(); } @Observer(value = { EventNames.GO_PERSONAL_WORKSPACE }, create = true) public void switchToUserWorkspace() { userWorkspacePath = getCurrentDocumentPath(); reset(); } @Observer(value = { EventNames.GO_HOME }, create = false) @BypassInterceptors public void switchToDocumentBase() { } public String forceTreeRefresh() throws IOException { resetCurrentDocumentData(); FacesContext context = FacesContext.getCurrentInstance(); HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); response.setContentType("application/xml; charset=UTF-8"); response.getWriter().write("<response>OK</response>"); context.responseComplete(); return null; } /** * @since 6.0 */ public void toggleListener() { FacesContext facesContext = FacesContext.getCurrentInstance(); Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap(); requestMap.put(NODE_SELECTED_MARKER, Boolean.TRUE); } /** * @since 6.0 */ public boolean isNodeExpandEvent() { FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext != null) { ExternalContext externalContext = facesContext.getExternalContext(); if (externalContext != null) { return Boolean.TRUE.equals(externalContext.getRequestMap().get( NODE_SELECTED_MARKER)); } } return false; } }