/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.web.bean; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.web.app.AlfrescoNavigationHandler; import org.alfresco.web.app.Application; import org.alfresco.web.app.context.IContextListener; import org.alfresco.web.app.context.UIContextService; import org.alfresco.web.app.servlet.DownloadContentServlet; import org.alfresco.web.bean.repository.MapNode; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.NodePropertyResolver; import org.alfresco.web.bean.repository.QNameNodeMap; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.wizard.NewSpaceWizard; import org.alfresco.web.config.ClientConfigElement; import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.common.Utils.URLMode; import org.alfresco.web.ui.common.component.IBreadcrumbHandler; import org.alfresco.web.ui.common.component.UIActionLink; import org.alfresco.web.ui.common.component.UIBreadcrumb; import org.alfresco.web.ui.common.component.UIModeList; import org.alfresco.web.ui.common.component.UIStatusMessage; import org.alfresco.web.ui.common.component.data.UIRichList; import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; import org.alfresco.web.ui.repo.component.UINodeDescendants; import org.alfresco.web.ui.repo.component.UINodePath; import org.alfresco.web.ui.repo.component.UISimpleSearch; import org.apache.log4j.Logger; import org.apache.log4j.Priority; /** * Bean providing properties and behaviour for the main folder/document browse screen and * search results screens. * * @author Kevin Roast */ public class BrowseBean implements IContextListener { // ------------------------------------------------------------------------------ // Construction /** * Default Constructor */ public BrowseBean() { UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); initFromClientConfig(); } // ------------------------------------------------------------------------------ // Bean property getters and setters /** * @param nodeService The NodeService to set. */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } /** * @param searchService The Searcher to set. */ public void setSearchService(SearchService searchService) { this.searchService = searchService; } /** * @param lockService The Lock Service to set. */ public void setLockService(LockService lockService) { this.lockService = lockService; } /** * @param navigator The NavigationBean to set. */ public void setNavigator(NavigationBean navigator) { this.navigator = navigator; } /** * @param dictionaryService The DictionaryService to set. */ public void setDictionaryService(DictionaryService dictionaryService) { this.dictionaryService = dictionaryService; } /** * @param fileFolderService The FileFolderService to set. */ public void setFileFolderService(FileFolderService fileFolderService) { this.fileFolderService = fileFolderService; } /** * @return Returns the browse View mode. See UIRichList */ public String getBrowseViewMode() { return this.browseViewMode; } /** * @param browseViewMode The browse View mode to set. See UIRichList. */ public void setBrowseViewMode(String browseViewMode) { this.browseViewMode = browseViewMode; } /** * @return Returns true if dashboard view is available for the current node. */ public boolean isDashboardView() { return this.dashboardView; } /** * @param dashboardView The dashboard view mode to set. */ public void setDashboardView(boolean dashboardView) { this.dashboardView = dashboardView; if (dashboardView == true) { FacesContext fc = FacesContext.getCurrentInstance(); fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dashboard"); } else { navigateBrowseScreen(); } } /** * @return Returns the browsePageSize. */ public int getBrowsePageSize() { return this.browsePageSize; } /** * @param browsePageSize The browsePageSize to set. */ public void setBrowsePageSize(int browsePageSize) { this.browsePageSize = browsePageSize; } /** * @return Returns the minimum length of a valid search string. */ public int getMinimumSearchLength() { return this.clientConfig.getSearchMinimum(); } /** * @return Returns the Space Node being used for the current browse screen action. */ public Node getActionSpace() { return this.actionSpace; } /** * @param actionSpace Set the Space Node to be used for the current browse screen action. */ public void setActionSpace(Node actionSpace) { this.actionSpace = actionSpace; } /** * @return The document node being used for the current operation */ public Node getDocument() { return this.document; } /** * @param document The document node to be used for the current operation */ public void setDocument(Node document) { this.document = document; } /** * @param contentRichList The contentRichList to set. */ public void setContentRichList(UIRichList browseRichList) { this.contentRichList = browseRichList; if (this.contentRichList != null) { this.contentRichList.setInitialSortColumn( this.clientConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); this.contentRichList.setInitialSortDescending( this.clientConfig.hasDescendingSort(PAGE_NAME_BROWSE)); } } /** * @return Returns the contentRichList. */ public UIRichList getContentRichList() { return this.contentRichList; } /** * @param spacesRichList The spacesRichList to set. */ public void setSpacesRichList(UIRichList detailsRichList) { this.spacesRichList = detailsRichList; if (this.spacesRichList != null) { // set the initial sort column and direction this.spacesRichList.setInitialSortColumn( this.clientConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); this.spacesRichList.setInitialSortDescending( this.clientConfig.hasDescendingSort(PAGE_NAME_BROWSE)); } } /** * @return Returns the spacesRichList. */ public UIRichList getSpacesRichList() { return this.spacesRichList; } /** * @return Returns the statusMessage component. */ public UIStatusMessage getStatusMessage() { return this.statusMessage; } /** * @param statusMessage The statusMessage component to set. */ public void setStatusMessage(UIStatusMessage statusMessage) { this.statusMessage = statusMessage; } /** * @return Returns the deleteMessage. */ public String getDeleteMessage() { return this.deleteMessage; } /** * @param deleteMessage The deleteMessage to set. */ public void setDeleteMessage(String deleteMessage) { this.deleteMessage = deleteMessage; } /** * Page accessed bean method to get the container nodes currently being browsed * * @return List of container Node objects for the current browse location */ public List getNodes() { // the references to container nodes and content nodes are transient for one use only // we do this so we only query/search once - as we cannot distinguish between node types // until after the query. The logic is a bit confusing but otherwise we would need to // perform the same query or search twice for every screen refresh. if (this.containerNodes == null) { if (this.navigator.getSearchContext() == null) { queryBrowseNodes(this.navigator.getCurrentNodeId()); } else { searchBrowseNodes(this.navigator.getSearchContext()); } } List result = this.containerNodes; // we clear the member variable during invalidateComponents() return result; } /** * Page accessed bean method to get the content nodes currently being browsed * * @return List of content Node objects for the current browse location */ public List getContent() { // see comment in getNodes() above for reasoning here if (this.contentNodes == null) { if (this.navigator.getSearchContext() == null) { queryBrowseNodes(this.navigator.getCurrentNodeId()); } else { searchBrowseNodes(this.navigator.getSearchContext()); } } List result = this.contentNodes; // we clear the member variable during invalidateComponents() return result; } /** * Setup the additional properties required at data-binding time. *

* These are properties used by components on the page when iterating over the nodes. * Information such as whether the node is locked, a working copy, download URL etc. *

* We use a set of anonymous inner classes to provide the implemention for the property * getters. The interfaces are only called when the properties are first required. * * @param node MapNode to add the properties too */ public void setupDataBindingProperties(MapNode node) { // special properties to be used by the value binding components on the page node.addPropertyResolver("locked", this.resolverlocked); node.addPropertyResolver("owner", this.resolverOwner); node.addPropertyResolver("workingCopy", this.resolverWorkingCopy); node.addPropertyResolver("url", this.resolverUrl); node.addPropertyResolver("fileType16", this.resolverFileType16); node.addPropertyResolver("fileType32", this.resolverFileType32); node.addPropertyResolver("size", this.resolverSize); node.addPropertyResolver("cancelCheckOut", this.resolverCancelCheckOut); node.addPropertyResolver("checkIn", this.resolverCheckIn); node.addPropertyResolver("beingDiscussed", this.resolverBeingDiscussed); node.addPropertyResolver("editLinkType", this.resolverEditLinkType); node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl); node.addPropertyResolver("cifsPath", this.resolverCifsPath); } // ------------------------------------------------------------------------------ // IContextListener implementation /** * @see org.alfresco.web.app.context.IContextListener#contextUpdated() */ public void contextUpdated() { invalidateComponents(); } // ------------------------------------------------------------------------------ // Navigation action event handlers /** * Change the current view mode based on user selection * * @param event ActionEvent */ public void viewModeChanged(ActionEvent event) { UIModeList viewList = (UIModeList)event.getComponent(); // get the view mode ID String viewMode = viewList.getValue().toString(); if (VIEWMODE_DASHBOARD.equals(viewMode) == false) { // set the page size based on the style of display setBrowsePageSize(this.clientConfig.getDefaultPageSize(PAGE_NAME_BROWSE, viewMode)); if (logger.isDebugEnabled()) logger.debug("Browse view page size set to: " + getBrowsePageSize()); // in case we left for dashboard if (isDashboardView() == true) { setDashboardView(false); } // push the view mode into the lists setBrowseViewMode(viewMode); } else { // special case for Dashboard view setDashboardView(true); } } // ------------------------------------------------------------------------------ // Helper methods /** * Query a list of nodes for the specified parent node Id * * @param parentNodeId Id of the parent node or null for the root node */ private void queryBrowseNodes(String parentNodeId) { long startTime = 0; if (logger.isDebugEnabled()) startTime = System.currentTimeMillis(); UserTransaction tx = null; try { FacesContext context = FacesContext.getCurrentInstance(); tx = Repository.getUserTransaction(context, true); tx.begin(); NodeRef parentRef; if (parentNodeId == null) { // no specific parent node specified - use the root node parentRef = this.nodeService.getRootNode(Repository.getStoreRef()); } else { // build a NodeRef for the specified Id and our store parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId); } List childRefs = this.nodeService.getChildAssocs(parentRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); this.containerNodes = new ArrayList(childRefs.size()); this.contentNodes = new ArrayList(childRefs.size()); for (ChildAssociationRef ref: childRefs) { // create our Node representation from the NodeRef NodeRef nodeRef = ref.getChildRef(); if (this.nodeService.exists(nodeRef)) { // find it's type so we can see if it's a node we are interested in QName type = this.nodeService.getType(nodeRef); // make sure the type is defined in the data dictionary TypeDefinition typeDef = this.dictionaryService.getType(type); if (typeDef != null) { // look for Space or File nodes if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) { // create our Node representation MapNode node = new MapNode(nodeRef, this.nodeService, true); node.addPropertyResolver("icon", this.resolverSpaceIcon); node.addPropertyResolver("beingDiscussed", this.resolverBeingDiscussed); this.containerNodes.add(node); } else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) { // create our Node representation MapNode node = new MapNode(nodeRef, this.nodeService, true); setupDataBindingProperties(node); this.contentNodes.add(node); } } else { if (logger.isEnabledFor(Priority.WARN)) logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); } } } // commit the transaction tx.commit(); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); this.containerNodes = Collections.emptyList(); this.contentNodes = Collections.emptyList(); try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} } catch (Throwable err) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); this.containerNodes = Collections.emptyList(); this.contentNodes = Collections.emptyList(); try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} } if (logger.isDebugEnabled()) { long endTime = System.currentTimeMillis(); logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); } } /** * Search for a list of nodes using the specific search context * * @param searchContext To use to perform the search */ private void searchBrowseNodes(SearchContext searchContext) { long startTime = 0; if (logger.isDebugEnabled()) startTime = System.currentTimeMillis(); // get the searcher object to build the query String query = searchContext.buildQuery(getMinimumSearchLength()); if (query == null) { // failed to build a valid query, the user probably did not enter the // minimum text required to construct a valid search Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_MINIMUM), new Object[] {getMinimumSearchLength()})); this.containerNodes = Collections.emptyList(); this.contentNodes = Collections.emptyList(); return; } // perform the search against the repo UserTransaction tx = null; ResultSet results = null; try { tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); tx.begin(); results = this.searchService.query( Repository.getStoreRef(), "lucene", query, null, null); if (logger.isDebugEnabled()) logger.debug("Search results returned: " + results.length()); // create a list of items from the results this.containerNodes = new ArrayList(results.length()); this.contentNodes = new ArrayList(results.length()); if (results.length() != 0) { for (ResultSetRow row: results) { NodeRef nodeRef = row.getNodeRef(); if (this.nodeService.exists(nodeRef)) { // find it's type so we can see if it's a node we are interested in QName type = this.nodeService.getType(nodeRef); // make sure the type is defined in the data dictionary TypeDefinition typeDef = this.dictionaryService.getType(type); if (typeDef != null) { // look for Space or File nodes if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) { // create our Node representation MapNode node = new MapNode(nodeRef, this.nodeService, true); // construct the path to this Node node.addPropertyResolver("path", this.resolverPath); node.addPropertyResolver("displayPath", this.resolverDisplayPath); node.addPropertyResolver("icon", this.resolverSpaceIcon); node.addPropertyResolver("beingDiscussed", this.resolverBeingDiscussed); this.containerNodes.add(node); } else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) { // create our Node representation MapNode node = new MapNode(nodeRef, this.nodeService, true); setupDataBindingProperties(node); // construct the path to this Node node.addPropertyResolver("path", this.resolverPath); node.addPropertyResolver("displayPath", this.resolverDisplayPath); this.contentNodes.add(node); } } else { if (logger.isEnabledFor(Priority.WARN)) logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); } } else { if (logger.isEnabledFor(Priority.WARN)) logger.warn("Missing object returned from search indexes: id = " + nodeRef + " search query: " + query); } } } // commit the transaction tx.commit(); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); this.containerNodes = Collections.emptyList(); this.contentNodes = Collections.emptyList(); try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} } catch (Throwable err) { logger.info("Search failed for: " + query); Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH), new Object[] {err.getMessage()}), err ); this.containerNodes = Collections.emptyList(); this.contentNodes = Collections.emptyList(); try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} } finally { if (results != null) { results.close(); } } if (logger.isDebugEnabled()) { long endTime = System.currentTimeMillis(); logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); } } // ------------------------------------------------------------------------------ // Property Resolvers public NodePropertyResolver resolverlocked = new NodePropertyResolver() { public Object get(Node node) { return Repository.isNodeLocked(node, lockService); } }; public NodePropertyResolver resolverOwner = new NodePropertyResolver() { public Object get(Node node) { return Repository.isNodeOwner(node, lockService); } }; public NodePropertyResolver resolverCancelCheckOut = new NodePropertyResolver() { public Object get(Node node) { return node.hasAspect(ContentModel.ASPECT_WORKING_COPY) && node.hasPermission(PermissionService.CANCEL_CHECK_OUT); } }; public NodePropertyResolver resolverCheckIn = new NodePropertyResolver() { public Object get(Node node) { return node.hasAspect(ContentModel.ASPECT_WORKING_COPY) && node.hasPermission(PermissionService.CHECK_IN); } }; public NodePropertyResolver resolverWorkingCopy = new NodePropertyResolver() { public Object get(Node node) { return node.hasAspect(ContentModel.ASPECT_WORKING_COPY); } }; public NodePropertyResolver resolverBeingDiscussed = new NodePropertyResolver() { public Object get(Node node) { return node.hasAspect(ForumModel.ASPECT_DISCUSSABLE); } }; public NodePropertyResolver resolverDownload = new NodePropertyResolver() { public Object get(Node node) { return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); } }; public NodePropertyResolver resolverUrl = new NodePropertyResolver() { public Object get(Node node) { return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); } }; public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() { public Object get(Node node) { return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV); } }; public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() { public Object get(Node node) { return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS); } }; public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() { public Object get(Node node) { return Utils.getFileTypeImage(node.getName(), true); } }; public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() { public Object get(Node node) { return Utils.getFileTypeImage(node.getName(), false); } }; public NodePropertyResolver resolverPath = new NodePropertyResolver() { public Object get(Node node) { return nodeService.getPath(node.getNodeRef()); } }; public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() { public Object get(Node node) { // TODO: replace this with a method that shows the full display name - not QNames return Repository.getDisplayPath( (Path)node.getProperties().get("path") ); } }; public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() { public Object get(Node node) { QNameNodeMap props = (QNameNodeMap)node.getProperties(); String icon = (String)props.getRaw("app:icon"); return (icon != null ? icon : NewSpaceWizard.SPACE_ICON_DEFAULT); } }; public NodePropertyResolver resolverMimetype = new NodePropertyResolver() { public Object get(Node node) { ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); return (content != null ? content.getMimetype() : null); } }; public NodePropertyResolver resolverSize = new NodePropertyResolver() { public Object get(Node node) { ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); return (content != null ? new Long(content.getSize()) : null); } }; public NodePropertyResolver resolverEditLinkType = new NodePropertyResolver() { public Object get(Node node) { String editLinkType = "http"; // if the node is inline editable, the default http behaviour should // always be used otherwise the configured approach is used if (node.hasAspect(ContentModel.ASPECT_INLINEEDITABLE) == false) { editLinkType = clientConfig.getEditLinkType(); if (editLinkType == null) { editLinkType = "http"; } } return editLinkType; } }; // ------------------------------------------------------------------------------ // Navigation action event handlers /** * Action called from the Simple Search component. * Sets up the SearchContext object with the values from the simple search menu. */ public void search(ActionEvent event) { // setup the search text string on the top-level navigation handler UISimpleSearch search = (UISimpleSearch)event.getComponent(); this.navigator.setSearchContext(search.getSearchContext()); navigateBrowseScreen(); } /** * Action called to Close the search dialog by returning to the last view node Id */ public void closeSearch(ActionEvent event) { // set the current node Id ready for page refresh this.navigator.setCurrentNodeId( this.navigator.getCurrentNodeId() ); } /** * Action called when a folder space is clicked. * Navigate into the space. */ public void clickSpace(ActionEvent event) { UIActionLink link = (UIActionLink)event.getComponent(); Map params = link.getParameterMap(); String id = params.get("id"); if (id != null && id.length() != 0) { try { NodeRef ref = new NodeRef(Repository.getStoreRef(), id); clickSpace(ref); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); } } } /** * Action called when a folder space is clicked. * * @param nodeRef The node being clicked */ public void clickSpace(NodeRef nodeRef) { // refresh UI based on node selection updateUILocation(nodeRef); } /** * Handler called when a path element is clicked - navigate to the appropriate Space */ public void clickSpacePath(ActionEvent event) { UINodePath.PathElementEvent pathEvent = (UINodePath.PathElementEvent)event; NodeRef ref = pathEvent.NodeReference; try { // refresh UI based on node selection this.updateUILocation(ref); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {ref.getId()}) ); } } /** * Action called when a folders direct descendant (in the 'list' browse mode) is clicked. * Navigate into the the descendant space. */ public void clickDescendantSpace(ActionEvent event) { UINodeDescendants.NodeSelectedEvent nodeEvent = (UINodeDescendants.NodeSelectedEvent)event; NodeRef nodeRef = nodeEvent.NodeReference; if (nodeRef == null) { throw new IllegalStateException("NodeRef returned from UINodeDescendants.NodeSelectedEvent cannot be null!"); } if (logger.isDebugEnabled()) logger.debug("Selected noderef Id: " + nodeRef.getId()); try { // user can either select a descendant of a node display on the page which means we // must add the it's parent and itself to the breadcrumb List location = this.navigator.getLocation(); ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); if (logger.isDebugEnabled()) { logger.debug("Selected item getPrimaryParent().getChildRef() noderef Id: " + parentAssocRef.getChildRef().getId()); logger.debug("Selected item getPrimaryParent().getParentRef() noderef Id: " + parentAssocRef.getParentRef().getId()); logger.debug("Current value getNavigator().getCurrentNodeId() noderef Id: " + this.navigator.getCurrentNodeId()); } if (nodeEvent.IsParent == false) { // a descendant of the displayed node was selected // first refresh based on the parent and add to the breadcrumb updateUILocation(parentAssocRef.getParentRef()); // now add our selected node updateUILocation(nodeRef); } else { // else the parent ellipses i.e. the displayed node was selected updateUILocation(nodeRef); } } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) ); } } /** * Action event called by all Browse actions that need to setup a Space context * before an action page/wizard is called. The context will be a Node in setActionSpace() which * can be retrieved on the action page from BrowseBean.getActionSpace(). * * @param event ActionEvent */ public void setupSpaceAction(ActionEvent event) { UIActionLink link = (UIActionLink)event.getComponent(); Map params = link.getParameterMap(); String id = params.get("id"); setupSpaceAction(id, true); } /** * Public helper to setup action pages with Space context * * @param id of the Space node to setup context for */ public void setupSpaceAction(String id, boolean invalidate) { if (id != null && id.length() != 0) { if (logger.isDebugEnabled()) logger.debug("Setup for action, setting current space to: " + id); try { // create the node ref, then our node representation NodeRef ref = new NodeRef(Repository.getStoreRef(), id); Node node = new Node(ref); // resolve icon in-case one has not been set node.addPropertyResolver("icon", this.resolverSpaceIcon); // prepare a node for the action context setActionSpace(node); // setup the dispatch context in case it is required this.navigator.setupDispatchContext(node); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); } } else { setActionSpace(null); } // clear the UI state in preparation for finishing the next action if (invalidate == true) { // use the context service to notify all registered beans UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); } } /** * Acrtion event called by Delete Space actions. We setup the action space as normal, then prepare * any special case message string to be shown to the user if they are trying to delete specific spaces. */ public void setupDeleteAction(ActionEvent event) { String message = null; setupSpaceAction(event); Node node = getActionSpace(); if (node != null) { NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); if (node.getNodeRef().equals(companyRootRef)) { message = Application.getMessage(FacesContext.getCurrentInstance(), MSG_DELETE_COMPANYROOT); } } setDeleteMessage(message); } /** * Action event called by all actions that need to setup a Content Document context on the * BrowseBean before an action page/wizard is called. The context will be a Node in * setDocument() which can be retrieved on the action page from BrowseBean.getDocument(). */ public void setupContentAction(ActionEvent event) { UIActionLink link = (UIActionLink)event.getComponent(); Map params = link.getParameterMap(); setupContentAction(params.get("id"), true); } /** * Public helper to setup action pages with content context * * @param id of the content node to setup context for */ public void setupContentAction(String id, boolean invalidate) { if (id != null && id.length() != 0) { if (logger.isDebugEnabled()) logger.debug("Setup for action, setting current document to: " + id); try { // create the node ref, then our node representation NodeRef ref = new NodeRef(Repository.getStoreRef(), id); Node node = new Node(ref); // store the URL to for downloading the content node.addPropertyResolver("url", this.resolverDownload); node.addPropertyResolver("fileType32", this.resolverFileType32); node.addPropertyResolver("mimetype", this.resolverMimetype); node.addPropertyResolver("size", this.resolverSize); node.addPropertyResolver("cancelCheckOut", this.resolverCancelCheckOut); node.addPropertyResolver("checkIn", this.resolverCheckIn); // get hold of the DocumentDetailsBean and reset it DocumentDetailsBean docDetails = (DocumentDetailsBean)FacesContext.getCurrentInstance(). getExternalContext().getSessionMap().get("DocumentDetailsBean"); if (docDetails != null) { docDetails.reset(); } // remember the document setDocument(node); // setup the dispatch context in case it is required this.navigator.setupDispatchContext(node); } catch (InvalidNodeRefException refErr) { Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); } } else { setDocument(null); } // clear the UI state in preparation for finishing the next action if (invalidate == true) { // use the context service to notify all registered beans UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); } } /** * Handler called upon the completion of the Delete Space page * * @return outcome */ public String deleteSpaceOK() { String outcome = null; Node node = getActionSpace(); if (node != null) { try { if (logger.isDebugEnabled()) logger.debug("Trying to delete space: " + node.getId()); this.nodeService.deleteNode(node.getNodeRef()); // remove this node from the breadcrumb if required List location = navigator.getLocation(); IBreadcrumbHandler handler = location.get(location.size() - 1); if (handler instanceof BrowseBreadcrumbHandler) { // see if the current breadcrumb location is our node if ( ((BrowseBreadcrumbHandler)handler).getNodeRef().equals(node.getNodeRef()) == true ) { location.remove(location.size() - 1); // now work out which node to set the list to refresh against if (location.size() != 0) { handler = location.get(location.size() - 1); if (handler instanceof BrowseBreadcrumbHandler) { // change the current node Id navigator.setCurrentNodeId(((BrowseBreadcrumbHandler)handler).getNodeRef().getId()); } else { // TODO: shouldn't do this - but for now the user home dir is the root! navigator.setCurrentNodeId(Application.getCurrentUser(FacesContext.getCurrentInstance()).getHomeSpaceId()); } } } } // add a message to inform the user that the delete was OK String statusMsg = MessageFormat.format( Application.getMessage(FacesContext.getCurrentInstance(), "status_space_deleted"), new Object[]{node.getName()}); Utils.addStatusMessage(FacesMessage.SEVERITY_INFO, statusMsg); // clear action context setActionSpace(null); // setting the outcome will show the browse view again outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME + AlfrescoNavigationHandler.DIALOG_SEPARATOR + "browse"; } catch (Throwable err) { Utils.addErrorMessage(Application.getMessage( FacesContext.getCurrentInstance(), MSG_ERROR_DELETE_SPACE) + err.getMessage(), err); } } else { logger.warn("WARNING: deleteSpaceOK called without a current Space!"); } return outcome; } /** * Handler called upon the completion of the Delete File page * * @return outcome */ public String deleteFileOK() { String outcome = null; Node node = getDocument(); if (node != null) { try { if (logger.isDebugEnabled()) logger.debug("Trying to delete content node: " + node.getId()); this.nodeService.deleteNode(node.getNodeRef()); // clear action context setDocument(null); // setting the outcome will show the browse view again outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME + AlfrescoNavigationHandler.DIALOG_SEPARATOR + "browse"; } catch (Throwable err) { Utils.addErrorMessage(Application.getMessage( FacesContext.getCurrentInstance(), MSG_ERROR_DELETE_FILE) + err.getMessage(), err); } } else { logger.warn("WARNING: deleteFileOK called without a current Document!"); } return outcome; } // ------------------------------------------------------------------------------ // Private helpers /** * Initialise default values from client configuration */ private void initFromClientConfig() { this.clientConfig = Application.getClientConfig(FacesContext.getCurrentInstance()); this.browseViewMode = clientConfig.getDefaultView(PAGE_NAME_BROWSE); this.browsePageSize = clientConfig.getDefaultPageSize(PAGE_NAME_BROWSE, this.browseViewMode); } /** * Refresh the UI after a Space selection change. Adds the selected space to the breadcrumb * location path and also updates the list components in the UI. * * @param ref NodeRef of the selected space */ /*package*/ void updateUILocation(NodeRef ref) { // get the current breadcrumb location and append a new handler to it // our handler know the ID of the selected node and the display label for it List location = this.navigator.getLocation(); if (location.size() != 0) { // attempt to find the ID - if it's already in the breadcrumb then we // navigate directly to that node - rather than add duplication to the breadcrumb path boolean foundNode = false; for (int i=0; i newLocation = new ArrayList(i+1); //newLocation.addAll(location.subList(0, i + 1)); //this.navigator.setLocation(newLocation); // TODO: but instead for now we do this: int count = location.size(); for (int n=i+1; n containerNodes = null; private List contentNodes = null; /** The current space and it's properties - if any */ private Node actionSpace; /** The current document */ private Node document; /** Special message to display when user deleting certain folders e.g. Company Home */ private String deleteMessage; /** The current browse view mode - set to a well known IRichListRenderer identifier */ private String browseViewMode; /** The current browse view page size */ private int browsePageSize; /** True if current space has a dashboard (template) view available */ private boolean dashboardView; }