org.alfresco.web.bean.BrowseBean.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.web.bean.BrowseBean.java

Source

/*
 * #%L
 * Alfresco Repository WAR Community
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.web.bean;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.transaction.UserTransaction;

import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.search.SearcherException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.web.scripts.FileTypeImageUtils;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
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.ml.MultilingualContentService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
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.search.LimitBy;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchParameters;
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.util.FileFilterMode;
import org.alfresco.util.FileFilterMode.Client;
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.app.servlet.FacesHelper;
import org.alfresco.web.bean.content.DocumentDetailsDialog;
import org.alfresco.web.bean.ml.MultilingualManageDialog;
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.search.SearchContext;
import org.alfresco.web.bean.spaces.CreateSpaceWizard;
import org.alfresco.web.bean.users.UserPreferencesBean;
import org.alfresco.web.config.ClientConfigElement;
import org.alfresco.web.config.ViewsConfigElement;
import org.alfresco.web.ui.common.PanelGenerator;
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.UIPanel.ExpandedEvent;
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.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.Config;
import org.springframework.extensions.config.ConfigElement;
import org.springframework.extensions.config.ConfigService;

/**
 * Bean providing properties and behaviour for the main folder/document browse screen and
 * search results screens.
 *
 * @author Kevin Roast
 */
public class BrowseBean implements IContextListener, Serializable {
    private static final long serialVersionUID = -3234262484615161360L;

    /** Public JSF Bean name */
    public static final String BEAN_NAME = "BrowseBean";

    // ------------------------------------------------------------------------------
    // 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;
    }

    protected NodeService getNodeService() {
        if (nodeService == null) {
            nodeService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService();
        }
        return nodeService;
    }

    /**
     * @param checkOutCheckInService The service for check-in and check-out.
     */
    public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) {
        this.checkOutCheckInService = checkOutCheckInService;
    }

    protected CheckOutCheckInService getCheckOutCheckInService() {
        if (checkOutCheckInService == null) {
            checkOutCheckInService = Repository.getServiceRegistry(FacesContext.getCurrentInstance())
                    .getCheckOutCheckInService();
        }
        return checkOutCheckInService;
    }

    /**
     * @param searchService The Searcher to set.
     */
    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    protected SearchService getSearchService() {
        if (searchService == null) {
            searchService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getSearchService();
        }
        return searchService;
    }

    /**
     * @param userPreferencesBean The UserPreferencesBean to set.
     */
    public void setUserPreferencesBean(UserPreferencesBean userPreferencesBean) {
        this.userPreferencesBean = userPreferencesBean;
    }

    /**
     * @param lockService The Lock Service to set.
     */
    public void setLockService(LockService lockService) {
        this.lockService = lockService;
    }

    protected LockService getLockService() {
        if (lockService == null) {
            lockService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getLockService();
        }
        return 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;
    }

    protected DictionaryService getDictionaryService() {
        if (dictionaryService == null) {
            dictionaryService = Repository.getServiceRegistry(FacesContext.getCurrentInstance())
                    .getDictionaryService();
        }
        return dictionaryService;
    }

    /**
     * @param multilingualContentService The Multilingual Content Service to set.
     */
    public void setMultilingualContentService(MultilingualContentService multilingualContentService) {
        this.multilingualContentService = multilingualContentService;
    }

    protected MultilingualContentService getMultilingualContentService() {
        if (multilingualContentService == null) {
            multilingualContentService = Repository.getServiceRegistry(FacesContext.getCurrentInstance())
                    .getMultilingualContentService();
        }
        return multilingualContentService;
    }

    /**
     * @param fileFolderService The FileFolderService to set.
     */
    public void setFileFolderService(FileFolderService fileFolderService) {
        this.fileFolderService = fileFolderService;
    }

    protected FileFolderService getFileFolderService() {
        if (fileFolderService == null) {
            fileFolderService = Repository.getServiceRegistry(FacesContext.getCurrentInstance())
                    .getFileFolderService();
        }
        return 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");
        }
    }

    public int getPageSizeContent() {
        return this.pageSizeContent;
    }

    public void setPageSizeContent(int pageSizeContent) {
        this.pageSizeContent = pageSizeContent;
        this.pageSizeContentStr = Integer.toString(pageSizeContent);
    }

    public int getPageSizeSpaces() {
        return this.pageSizeSpaces;
    }

    public void setPageSizeSpaces(int pageSizeSpaces) {
        this.pageSizeSpaces = pageSizeSpaces;
        this.pageSizeSpacesStr = Integer.toString(pageSizeSpaces);
    }

    public String getPageSizeContentStr() {
        return this.pageSizeContentStr;
    }

    public void setPageSizeContentStr(String pageSizeContentStr) {
        this.pageSizeContentStr = pageSizeContentStr;
    }

    public String getPageSizeSpacesStr() {
        return this.pageSizeSpacesStr;
    }

    public void setPageSizeSpacesStr(String pageSizeSpacesStr) {
        this.pageSizeSpacesStr = pageSizeSpacesStr;
    }

    /**
     * @return Returns the minimum length of a valid search string.
     */
    public static int getMinimumSearchLength() {
        return Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMinimum();
    }

    /**
     * @return Returns the panels expanded state map.
     */
    public Map<String, Boolean> getPanels() {
        return this.panels;
    }

    /**
     * @param panels The panels expanded state map.
     */
    public void setPanels(Map<String, Boolean> panels) {
        this.panels = panels;
    }

    /**
     * @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) {
        if (actionSpace != null) {
            for (NodeEventListener listener : getNodeEventListeners()) {
                listener.created(actionSpace, actionSpace.getType());
            }
        }
        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) {
        if (document != null) {
            for (NodeEventListener listener : getNodeEventListeners()) {
                listener.created(document, document.getType());
            }
        }
        this.document = document;
    }

    /**
     * @param contentRichList The contentRichList to set.
     */
    public void setContentRichList(UIRichList contentRichList) {
        this.contentRichList = contentRichList;
        if (this.contentRichList != null) {
            this.contentRichList.setInitialSortColumn(this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE));
            this.contentRichList.setInitialSortDescending(this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE));
        }
        // special case to handle an External Access URL
        // these URLs restart the JSF lifecycle but an old UIRichList is restored from
        // the component tree - which needs clearing "late" in the lifecycle process
        if (externalForceRefresh) {
            this.contentRichList.setValue(null);
            externalForceRefresh = false;
        }
    }

    /**
     * @return Returns the contentRichList.
     */
    public UIRichList getContentRichList() {
        return this.contentRichList;
    }

    /**
     * @param spacesRichList The spacesRichList to set.
     */
    public void setSpacesRichList(UIRichList spacesRichList) {
        this.spacesRichList = spacesRichList;
        if (this.spacesRichList != null) {
            // set the initial sort column and direction
            this.spacesRichList.setInitialSortColumn(this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE));
            this.spacesRichList.setInitialSortDescending(this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE));
        }
        if (externalForceRefresh) {
            this.spacesRichList.setValue(null);
        }
    }

    /**
     * @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<Node> 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<Node> 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<Node> 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<Node> result = this.contentNodes;

        // we clear the member variable during invalidateComponents()

        return result;
    }

    /**
     * Page accessed bean method to get the parent container nodes currently being browsed
     *
     * @return List of parent container Node objects for the current browse location
     */
    public List<Node> getParentNodes(NodeRef currNodeRef) {
        // As per AWC-1507 there are two scenarios for navigating to the space details. First
        // scenario is to show space details of the current space. Second scenario is to show 
        // space details of a child space of the current space. For now, added an extra query
        // so that existing context remains unaffected for second scenario, although it does 
        // mean that in first scenario there will be an extra query even though parentContainerNodes
        // and containerNodes will contain the same list.

        if (this.parentContainerNodes == null) {
            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 = getNodeService().getPrimaryParent(currNodeRef).getParentRef();

                List<FileInfo> children = this.getFileFolderService().list(parentRef);
                this.parentContainerNodes = new ArrayList<Node>(children.size());
                for (FileInfo fileInfo : children) {
                    // create our Node representation from the NodeRef
                    NodeRef nodeRef = fileInfo.getNodeRef();

                    // find it's type so we can see if it's a node we are interested in
                    QName type = this.getNodeService().getType(nodeRef);

                    // make sure the type is defined in the data dictionary
                    TypeDefinition typeDef = this.getDictionaryService().getType(type);

                    if (typeDef != null) {
                        MapNode node = null;

                        // look for Space folder node
                        if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER) == true
                                && this.getDictionaryService().isSubClass(type,
                                        ContentModel.TYPE_SYSTEM_FOLDER) == false) {
                            // create our Node representation
                            node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                            node.addPropertyResolver("icon", this.resolverSpaceIcon);
                            node.addPropertyResolver("smallIcon", this.resolverSmallIcon);

                            this.parentContainerNodes.add(node);
                        } else if (ApplicationModel.TYPE_FOLDERLINK.equals(type)) {
                            // create our Folder Link Node representation
                            node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                            node.addPropertyResolver("icon", this.resolverSpaceIcon);
                            node.addPropertyResolver("smallIcon", this.resolverSmallIcon);

                            this.parentContainerNodes.add(node);
                        }
                    } else {
                        if (logger.isWarnEnabled())
                            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() }), refErr);
                this.parentContainerNodes = Collections.<Node>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.parentContainerNodes = Collections.<Node>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 parent nodes: " + (endTime - startTime) + "ms");
            }
        }

        List<Node> result = this.parentContainerNodes;

        // we clear the member variable during invalidateComponents()

        return result;
    }

    /**
     * Determines whether the current space is a 'Sites' space
     * 
     * @return true if the current space is a 'Sites' space
     */
    public boolean isSitesSpace() {
        boolean siteSpace = false;

        Node currentNode = this.navigator.getCurrentNode();
        if (currentNode != null) {
            // check the type of the node to see if it is a 'site' related space
            QName currentNodeType = currentNode.getType();

            if (SiteModel.TYPE_SITES.isMatch(currentNodeType) || SiteModel.TYPE_SITE.isMatch(currentNodeType)
                    || getDictionaryService().isSubClass(currentNodeType, SiteModel.TYPE_SITE)) {
                siteSpace = true;
            }
        }

        return siteSpace;
    }

    /**
     * Returns the HTML to display if a space is a 'Sites' space
     * 
     * @return The HTML to display
     */
    public String getSitesSpaceWarningHTML() {
        FacesContext context = FacesContext.getCurrentInstance();
        String contextPath = context.getExternalContext().getRequestContextPath();
        StringBuilder html = new StringBuilder();

        try {
            html.append("<tr valign='top'>");
            html.append("<td style='background-image: url(");
            html.append(contextPath);
            html.append("/images/parts/whitepanel_4.gif)' ");
            html.append("width='4'></td><td style='padding:4px'>");

            StringWriter writer = new StringWriter();
            PanelGenerator.generatePanelStart(writer, contextPath, "yellowInner", "#ffffcc");
            html.append(writer.toString());

            html.append("<table cellpadding='0' cellspacing='0' border='0' width='100%'>");
            html.append("<tr><td valign='top' style='padding-top: 2px' width='20'>");
            html.append("<img src='");
            html.append(contextPath);
            html.append("/images/icons/warning.gif' width='16' height='16' /></td>");
            html.append("<td class='mainSubText'>");
            html.append(Application.getMessage(context, "sites_space_warning"));
            html.append("</td></tr></table>");

            writer = new StringWriter();
            PanelGenerator.generatePanelEnd(writer, contextPath, "yellowInner");
            html.append(writer.toString());

            html.append("</td><td style='background-image: url(");
            html.append(contextPath);
            html.append("/images/parts/whitepanel_6.gif)'");
            html.append("width='4'></td></tr>");
        } catch (IOException ioe) {
            logger.error(ioe);
        }

        return html.toString();
    }

    /**
     * Setup the common properties required at data-binding time.
     * <p>
     * These are properties used by components on the page when iterating over the nodes.
     * The properties are available as the Node is a Map so they can be accessed directly
     * by name. Information such as download URL, size and filetype are provided etc.
     * <p>
     * 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 requested.
     *
     * @param node       Node to add the properties too
     */
    public void setupCommonBindingProperties(Node node) {
        // special properties to be used by the value binding components on the page
        node.addPropertyResolver("url", this.resolverUrl);

        if (ApplicationModel.TYPE_FILELINK.equals(node.getType())) {
            node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
        } else {
            node.addPropertyResolver("downloadUrl", this.resolverDownload);
        }

        node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl);
        node.addPropertyResolver("cifsPath", this.resolverCifsPath);
        node.addPropertyResolver("fileType16", this.resolverFileType16);
        node.addPropertyResolver("fileType32", this.resolverFileType32);
        node.addPropertyResolver("size", this.resolverSize);
        node.addPropertyResolver("lang", this.resolverLang);
    }

    // ------------------------------------------------------------------------------
    // IContextListener implementation

    /**
     * @see org.alfresco.web.app.context.IContextListener#contextUpdated()
     */
    public void contextUpdated() {
        invalidateComponents();
    }

    /**
     * @see org.alfresco.web.app.context.IContextListener#areaChanged()
     */
    public void areaChanged() {
        // nothing to do
    }

    /**
     * @see org.alfresco.web.app.context.IContextListener#spaceChanged()
     */
    public void spaceChanged() {
        // nothing to do
    }

    // ------------------------------------------------------------------------------
    // NodeEventListener listeners

    /**
     * Add a listener to those called by the BrowseBean when nodes are created
     */
    public void addNodeEventListener(NodeEventListener listener) {
        if (this.nodeEventListeners == null) {
            this.nodeEventListeners = new HashSet<NodeEventListener>();
        }
        this.nodeEventListeners.add(listener);
    }

    /**
     * Remove a listener from the list of those called by BrowseBean
     */
    public void removeNodeEventListener(NodeEventListener listener) {
        if (this.nodeEventListeners != null) {
            this.nodeEventListeners.remove(listener);
        }
    }

    // ------------------------------------------------------------------------------
    // 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
            int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, viewMode);
            setPageSizeContent(pageSize);
            setPageSizeSpaces(pageSize);

            if (logger.isDebugEnabled())
                logger.debug("Browse view page size set to: " + pageSize);

            setDashboardView(false);

            // push the view mode into the lists
            setBrowseViewMode(viewMode);

            // setup dispatch context for custom views
            this.navigator.setupDispatchContext(this.navigator.getCurrentNode());

            // browse to appropriate view
            FacesContext fc = FacesContext.getCurrentInstance();
            String outcome = null;
            String viewId = fc.getViewRoot().getViewId();
            if (viewId.equals(BROWSE_VIEW_ID) == false && viewId.equals(CATEGORY_VIEW_ID) == false) {
                outcome = "browse";
            }
            fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome);
        } 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.getNodeService().getRootNode(Repository.getStoreRef());
            } else {
                // build a NodeRef for the specified Id and our store
                parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId);
            }

            List<FileInfo> children = null;
            FileFilterMode.setClient(Client.webclient);
            try {
                children = this.getFileFolderService().list(parentRef);
            } finally {
                FileFilterMode.clearClient();
            }

            this.containerNodes = new ArrayList<Node>(children.size());
            this.contentNodes = new ArrayList<Node>(children.size());

            // in case of dynamic config, only lookup once
            Set<NodeEventListener> nodeEventListeners = getNodeEventListeners();

            for (FileInfo fileInfo : children) {
                // create our Node representation from the NodeRef
                NodeRef nodeRef = fileInfo.getNodeRef();

                // find it's type so we can see if it's a node we are interested in
                QName type = this.getNodeService().getType(nodeRef);

                // make sure the type is defined in the data dictionary
                TypeDefinition typeDef = this.getDictionaryService().getType(type);

                if (typeDef != null) {
                    MapNode node = null;

                    // look for File content node
                    if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_CONTENT)) {
                        // create our Node representation
                        node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                        setupCommonBindingProperties(node);

                        this.contentNodes.add(node);
                    }
                    // look for Space folder node
                    else if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER) == true && this
                            .getDictionaryService().isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) {
                        // create our Node representation
                        node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                        node.addPropertyResolver("icon", this.resolverSpaceIcon);
                        node.addPropertyResolver("smallIcon", this.resolverSmallIcon);

                        this.containerNodes.add(node);
                    }
                    // look for File Link object node
                    else if (ApplicationModel.TYPE_FILELINK.equals(type)) {
                        // create our File Link Node representation
                        node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                        // only display the user has the permissions to navigate to the target of the link
                        NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
                        if (destRef != null && new Node(destRef).hasPermission(PermissionService.READ) == true) {
                            node.addPropertyResolver("url", this.resolverLinkUrl);
                            node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
                            node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl);
                            node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath);
                            node.addPropertyResolver("fileType16", this.resolverFileType16);
                            node.addPropertyResolver("fileType32", this.resolverFileType32);
                            node.addPropertyResolver("lang", this.resolverLang);

                            this.contentNodes.add(node);
                        }
                    } else if (ApplicationModel.TYPE_FOLDERLINK.equals(type)) {
                        // create our Folder Link Node representation
                        node = new MapNode(nodeRef, this.getNodeService(), fileInfo.getProperties());
                        // only display the user has the permissions to navigate to the target of the link
                        NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
                        if (destRef != null && new Node(destRef).hasPermission(PermissionService.READ) == true) {
                            node.addPropertyResolver("icon", this.resolverSpaceIcon);
                            node.addPropertyResolver("smallIcon", this.resolverSmallIcon);

                            this.containerNodes.add(node);
                        }
                    }

                    // inform any listeners that a Node wrapper has been created
                    if (node != null) {
                        for (NodeEventListener listener : nodeEventListeners) {
                            listener.created(node, type);
                        }
                    }
                } else {
                    if (logger.isWarnEnabled())
                        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() }), refErr);
            this.containerNodes = Collections.<Node>emptyList();
            this.contentNodes = Collections.<Node>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.<Node>emptyList();
            this.contentNodes = Collections.<Node>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.<Node>emptyList();
            this.contentNodes = Collections.<Node>emptyList();
            return;
        }

        // perform the search against the repo
        UserTransaction tx = null;
        ResultSet results = null;
        try {
            tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true);
            tx.begin();

            // build up the search parameters
            SearchParameters sp = new SearchParameters();
            sp.setLanguage(SearchService.LANGUAGE_LUCENE);
            sp.setQuery(query);
            sp.addStore(Repository.getStoreRef());

            // limit search results size as configured
            int searchLimit = Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMaxResults();
            if (searchLimit > 0) {
                sp.setLimitBy(LimitBy.FINAL_SIZE);
                sp.setLimit(searchLimit);
            }

            sp.setBulkFetchEnabled(
                    Application.getClientConfig(FacesContext.getCurrentInstance()).isBulkFetchEnabled());

            results = this.getSearchService().query(sp);
            if (logger.isDebugEnabled())
                logger.debug("Search results returned: " + results.length());

            // create a list of items from the results
            this.containerNodes = new ArrayList<Node>(results.length());
            this.contentNodes = new ArrayList<Node>(results.length());
            if (results.length() != 0) {
                // in case of dynamic config, only lookup once
                Set<NodeEventListener> nodeEventListeners = getNodeEventListeners();

                for (ResultSetRow row : results) {
                    NodeRef nodeRef = row.getNodeRef();

                    if (this.getNodeService().exists(nodeRef)) {
                        // find it's type so we can see if it's a node we are interested in
                        QName type = this.getNodeService().getType(nodeRef);

                        // make sure the type is defined in the data dictionary
                        TypeDefinition typeDef = this.getDictionaryService().getType(type);

                        if (typeDef != null) {
                            MapNode node = null;

                            // look for Space or File nodes
                            if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_FOLDER)
                                    && this.getDictionaryService().isSubClass(type,
                                            ContentModel.TYPE_SYSTEM_FOLDER) == false) {
                                // create our Node representation
                                node = new MapNode(nodeRef, this.getNodeService(), false);

                                node.addPropertyResolver("path", this.resolverPath);
                                node.addPropertyResolver("displayPath", this.resolverDisplayPath);
                                node.addPropertyResolver("icon", this.resolverSpaceIcon);
                                node.addPropertyResolver("smallIcon", this.resolverSmallIcon);

                                this.containerNodes.add(node);
                            } else if (this.getDictionaryService().isSubClass(type, ContentModel.TYPE_CONTENT)) {
                                // create our Node representation
                                node = new MapNode(nodeRef, this.getNodeService(), false);

                                setupCommonBindingProperties(node);

                                node.addPropertyResolver("path", this.resolverPath);
                                node.addPropertyResolver("displayPath", this.resolverDisplayPath);

                                this.contentNodes.add(node);
                            }
                            // look for File Link object node
                            else if (ApplicationModel.TYPE_FILELINK.equals(type)) {
                                // create our File Link Node representation
                                node = new MapNode(nodeRef, this.getNodeService(), false);
                                // only display the user has the permissions to navigate to the target of the link
                                NodeRef destRef = (NodeRef) node.getProperties()
                                        .get(ContentModel.PROP_LINK_DESTINATION);
                                if (new Node(destRef).hasPermission(PermissionService.READ) == true) {
                                    node.addPropertyResolver("url", this.resolverLinkUrl);
                                    node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
                                    node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl);
                                    node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath);
                                    node.addPropertyResolver("fileType16", this.resolverFileType16);
                                    node.addPropertyResolver("fileType32", this.resolverFileType32);
                                    node.addPropertyResolver("lang", this.resolverLang);
                                    node.addPropertyResolver("path", this.resolverPath);
                                    node.addPropertyResolver("displayPath", this.resolverDisplayPath);

                                    this.contentNodes.add(node);
                                }
                            } else if (ApplicationModel.TYPE_FOLDERLINK.equals(type)) {
                                // create our Folder Link Node representation
                                node = new MapNode(nodeRef, this.getNodeService(), false);
                                // only display the user has the permissions to navigate to the target of the link
                                NodeRef destRef = (NodeRef) node.getProperties()
                                        .get(ContentModel.PROP_LINK_DESTINATION);
                                if (new Node(destRef).hasPermission(PermissionService.READ) == true) {
                                    node.addPropertyResolver("icon", this.resolverSpaceIcon);
                                    node.addPropertyResolver("smallIcon", this.resolverSmallIcon);
                                    node.addPropertyResolver("path", this.resolverPath);
                                    node.addPropertyResolver("displayPath", this.resolverDisplayPath);

                                    this.containerNodes.add(node);
                                }
                            }

                            // inform any listeners that a Node wrapper has been created
                            if (node != null) {
                                for (NodeEventListener listener : nodeEventListeners) {
                                    listener.created(node, type);
                                }
                            }
                        } else {
                            if (logger.isWarnEnabled())
                                logger.warn(
                                        "Found invalid object in database: id = " + nodeRef + ", type = " + type);
                        }
                    } else {
                        if (logger.isWarnEnabled())
                            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() }), refErr);
            this.containerNodes = Collections.<Node>emptyList();
            this.contentNodes = Collections.<Node>emptyList();
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        } catch (SearcherException serr) {
            logger.info("Search failed for: " + query, serr);
            Utils.addErrorMessage(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_QUERY));
            this.containerNodes = Collections.<Node>emptyList();
            this.contentNodes = Collections.<Node>emptyList();
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        } catch (Throwable err) {
            Utils.addErrorMessage(MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH),
                    new Object[] { err.getMessage() }), err);
            this.containerNodes = Collections.<Node>emptyList();
            this.contentNodes = Collections.<Node>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 resolverDownload = new NodePropertyResolver() {
        private static final long serialVersionUID = 4048859853585650378L;

        public Object get(Node node) {
            return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName());
        }
    };

    public NodePropertyResolver resolverUrl = new NodePropertyResolver() {
        private static final long serialVersionUID = -5264085143622470386L;

        public Object get(Node node) {
            return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName());
        }
    };

    public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() {
        private static final long serialVersionUID = 9127234483419089006L;

        public Object get(Node node) {
            return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV);
        }
    };

    public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() {
        private static final long serialVersionUID = -5804924617772163104L;

        public Object get(Node node) {
            return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS);
        }
    };

    public NodePropertyResolver resolverLinkDownload = new NodePropertyResolver() {
        private static final long serialVersionUID = 7208696954599958859L;

        public Object get(Node node) {
            NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
            if (getNodeService().exists(destRef) == true) {
                String destName = Repository.getNameForNode(getNodeService(), destRef);
                return DownloadContentServlet.generateDownloadURL(destRef, destName);
            } else {
                // TODO: link object is missing - navigate to a page with appropriate message
                return "#";
            }
        }
    };

    public NodePropertyResolver resolverLinkUrl = new NodePropertyResolver() {
        private static final long serialVersionUID = -1280702397805414147L;

        public Object get(Node node) {
            NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
            if (getNodeService().exists(destRef) == true) {
                String destName = Repository.getNameForNode(getNodeService(), destRef);
                return DownloadContentServlet.generateBrowserURL(destRef, destName);
            } else {
                // TODO: link object is missing - navigate to a page with appropriate message
                return "#";
            }
        }
    };

    public NodePropertyResolver resolverLinkWebdavUrl = new NodePropertyResolver() {
        private static final long serialVersionUID = -3097558079118837397L;

        public Object get(Node node) {
            NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
            if (getNodeService().exists(destRef) == true) {
                return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.WEBDAV);
            } else {
                // TODO: link object is missing - navigate to a page with appropriate message
                return "#";
            }
        }
    };

    public NodePropertyResolver resolverLinkCifsPath = new NodePropertyResolver() {
        private static final long serialVersionUID = 673020173327603487L;

        public Object get(Node node) {
            NodeRef destRef = (NodeRef) node.getProperties().get(ContentModel.PROP_LINK_DESTINATION);
            if (getNodeService().exists(destRef) == true) {
                return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.CIFS);
            } else {
                // TODO: link object is missing - navigate to a page with appropriate message
                return "#";
            }
        }
    };

    public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() {
        private static final long serialVersionUID = -2690520488415178029L;

        public Object get(Node node) {
            return FileTypeImageUtils.getFileTypeImage(node.getName(), true);
        }
    };

    public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() {
        private static final long serialVersionUID = 1991254398502584389L;

        public Object get(Node node) {
            return FileTypeImageUtils.getFileTypeImage(node.getName(), false);
        }
    };

    public NodePropertyResolver resolverPath = new NodePropertyResolver() {
        private static final long serialVersionUID = 8008094870888545035L;

        public Object get(Node node) {
            return node.getNodePath();
        }
    };

    public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() {
        private static final long serialVersionUID = -918422848579179425L;

        public Object get(Node node) {
            // TODO: replace this with a method that shows the full display name - not QNames?
            return Repository.getDisplayPath(node.getNodePath());
        }
    };

    public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() {
        private static final long serialVersionUID = -5644418026591098018L;

        public Object get(Node node) {
            QNameNodeMap props = (QNameNodeMap) node.getProperties();
            String icon = (String) props.getRaw("app:icon");
            return (icon != null ? icon : CreateSpaceWizard.DEFAULT_SPACE_ICON_NAME);
        }
    };

    public NodePropertyResolver resolverSmallIcon = new NodePropertyResolver() {
        private static final long serialVersionUID = -150483121767183580L;

        public Object get(Node node) {
            QNameNodeMap props = (QNameNodeMap) node.getProperties();
            String icon = (String) props.getRaw("app:icon");
            return (icon != null ? icon + "-16" : SPACE_SMALL_DEFAULT);
        }
    };

    public NodePropertyResolver resolverMimetype = new NodePropertyResolver() {
        private static final long serialVersionUID = -8864267975247235172L;

        public Object get(Node node) {
            ContentData content = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT);
            return (content != null ? content.getMimetype() : null);
        }
    };

    public NodePropertyResolver resolverEncoding = new NodePropertyResolver() {
        private static final long serialVersionUID = -1130974681844152101L;

        public Object get(Node node) {
            ContentData content = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT);
            return (content != null ? content.getEncoding() : null);
        }
    };

    public NodePropertyResolver resolverSize = new NodePropertyResolver() {
        private static final long serialVersionUID = 1273541660444385276L;

        public Object get(Node node) {
            ContentData content = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT);
            return (content != null ? new Long(content.getSize()) : 0L);
        }
    };

    public NodePropertyResolver resolverLang = new NodePropertyResolver() {

        private static final long serialVersionUID = 5412446489528560367L;

        public Object get(Node node) {

            String lang = null;

            if (node.getAspects().contains(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)) {
                Locale locale = null;

                if (node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)) {
                    // if the translation is empty, the lang of the content is the lang of it's pivot.
                    NodeRef pivot = getMultilingualContentService().getPivotTranslation(node.getNodeRef());
                    locale = (Locale) getNodeService().getProperty(pivot, ContentModel.PROP_LOCALE);
                } else {
                    locale = (Locale) node.getProperties().get(ContentModel.PROP_LOCALE);
                }
                // the content filter lang defined by the user
                String userLang = userPreferencesBean.getContentFilterLanguage();
                // the node lang
                String nodeLang = locale.getLanguage();

                // if filter equals all languages : display the lang for each translation
                if (nodeLang == null) {
                    lang = nodeLang;
                }

                // if filter is different : display the lang
                else if (!nodeLang.equalsIgnoreCase(userLang)) {
                    lang = nodeLang;
                }

                // else if the filter is equal to the lang node : nothing to do [lang = null]
            }

            return lang;
        }
    };

    // ------------------------------------------------------------------------------
    // 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
        String currentNodeId = this.navigator.getCurrentNodeId();
        this.navigator.setCurrentNodeId(currentNodeId);

        // setup dispatch context so we go back to the right place
        NodeRef currentNodeRef = new NodeRef(Repository.getStoreRef(), currentNodeId);
        Node currentNode = new Node(currentNodeRef);
        this.navigator.setupDispatchContext(currentNode);
    }

    /**
     * Update page size based on user selection
     */
    public void updateSpacesPageSize(ActionEvent event) {
        try {
            int size = Integer.parseInt(this.pageSizeSpacesStr);
            if (size >= 0) {
                this.pageSizeSpaces = size;
            } else {
                // reset to known value if this occurs
                this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces);
            }
        } catch (NumberFormatException err) {
            // reset to known value if this occurs
            this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces);
        }
    }

    /**
     * Update page size based on user selection
     */
    public void updateContentPageSize(ActionEvent event) {
        try {
            int size = Integer.parseInt(this.pageSizeContentStr);
            if (size >= 0) {
                this.pageSizeContent = size;
            } else {
                // reset to known value if this occurs
                this.pageSizeContentStr = Integer.toString(this.pageSizeContent);
            }
        } catch (NumberFormatException err) {
            // reset to known value if this occurs
            this.pageSizeContentStr = Integer.toString(this.pageSizeContent);
        }
    }

    /**
     * Action called when a folder space is clicked.
     * Navigate into the space.
     */
    public void clickSpace(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        String id = params.get("id");
        if (id != null && id.length() != 0) {
            try {
                NodeRef ref = new NodeRef(Repository.getStoreRef(), id);

                // handle special folder link node case
                if (ApplicationModel.TYPE_FOLDERLINK.equals(this.getNodeService().getType(ref))) {
                    ref = (NodeRef) this.getNodeService().getProperty(ref, ContentModel.PROP_LINK_DESTINATION);
                }

                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
            ChildAssociationRef parentAssocRef = getNodeService().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<String, String> 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) {
            FacesContext fc = FacesContext.getCurrentInstance();
            NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc));
            if (node.getNodeRef().equals(companyRootRef)) {
                message = Application.getMessage(fc, 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<String, String> params = link.getParameterMap();
        setupContentAction(params.get("id"), true);
    }

    /**
     * Action event called by all actions that need to setup a <b>Multilingual</b> 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 setupMLContainerContentAction(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();

        String id = params.get("id");

        NodeRef translation = new NodeRef(Repository.getStoreRef(), id);

        // remember the bean from which the action comes
        FacesContext fc = FacesContext.getCurrentInstance();
        DocumentDetailsDialog docDetails = (DocumentDetailsDialog) FacesHelper.getManagedBean(fc,
                "DocumentDetailsDialog");
        docDetails.setTranslationDocument(new MapNode(translation));
        MultilingualManageDialog mmDialog = (MultilingualManageDialog) FacesHelper.getManagedBean(fc,
                "MultilingualManageDialog");
        mmDialog.setTranslationDocument(docDetails.getTranslationDocument());

        // set the ml container as the current document
        NodeRef mlContainer = getMultilingualContentService().getTranslationContainer(translation);

        setupContentAction(mlContainer.getId(), 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 MapNode(ref);

                // store the URL to for downloading the content
                if (ApplicationModel.TYPE_FILELINK.equals(node.getType())) {
                    node.addPropertyResolver("url", this.resolverLinkDownload);
                    node.addPropertyResolver("downloadUrl", this.resolverLinkDownload);
                } else {
                    node.addPropertyResolver("url", this.resolverDownload);
                    node.addPropertyResolver("downloadUrl", this.resolverDownload);
                }
                node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl);
                node.addPropertyResolver("cifsPath", this.resolverCifsPath);
                node.addPropertyResolver("fileType32", this.resolverFileType32);
                node.addPropertyResolver("mimetype", this.resolverMimetype);
                node.addPropertyResolver("encoding", this.resolverEncoding);
                node.addPropertyResolver("size", this.resolverSize);
                node.addPropertyResolver("lang", this.resolverLang);

                for (NodeEventListener listener : getNodeEventListeners()) {
                    listener.created(node, node.getType());
                }

                // get hold of the DocumentDetailsDialog and reset it
                DocumentDetailsDialog docDetails = (DocumentDetailsDialog) FacesContext.getCurrentInstance()
                        .getExternalContext().getSessionMap().get("DocumentDetailsDialog");
                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 }));
                throw new AbortProcessingException("Invalid node reference");
            }
        } 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();
        }
    }

    /**
     * Removes the given node from the breadcrumb i.e. following a delete
     *
     * @param node The space to remove from the breadcrumb
     */
    public void removeSpaceFromBreadcrumb(Node node) {
        List<IBreadcrumbHandler> location = navigator.getLocation();
        IBreadcrumbHandler handler = location.get(location.size() - 1);
        if (handler instanceof IRepoBreadcrumbHandler) {
            // see if the current breadcrumb location is our node
            if (((IRepoBreadcrumbHandler) 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 IRepoBreadcrumbHandler) {
                        // change the current node Id
                        navigator.setCurrentNodeId(((IRepoBreadcrumbHandler) handler).getNodeRef().getId());
                    } else {
                        // if we don't have access to the NodeRef to go to next then go to the home space
                        navigator.processToolbarLocation(NavigationBean.LOCATION_HOME, false);
                    }
                } else {
                    // if there is no breadcrumb left go to the user's home space
                    navigator.processToolbarLocation(NavigationBean.LOCATION_HOME, false);
                }
            }
        }
    }

    /**
     * Support for refresh of lists via special case for an External Access URL.
     * these URLs restart the JSF lifecycle but an old UIRichList is restored from
     * the component tree - which needs clearing "late" in the lifecycle process.
     */
    public void externalAccessRefresh() {
        this.externalForceRefresh = true;
    }

    /**
     * Save the state of the panel that was expanded/collapsed
     */
    public void expandPanel(ActionEvent event) {
        if (event instanceof ExpandedEvent) {
            String id = event.getComponent().getId();
            this.panels.put(id, ((ExpandedEvent) event).State);
        }
    }

    // ------------------------------------------------------------------------------
    // Private helpers

    /**
     * Initialise default values from client configuration
     */
    private void initFromClientConfig() {
        // TODO - review implications of these default values on dynamic/MT client: viewsConfig & browseViewMode, as well as page size content/spaces ...
        ConfigService config = Application.getConfigService(FacesContext.getCurrentInstance());

        this.viewsConfig = (ViewsConfigElement) config.getConfig("Views")
                .getConfigElement(ViewsConfigElement.CONFIG_ELEMENT_ID);

        this.browseViewMode = this.viewsConfig.getDefaultView(PAGE_NAME_BROWSE);
        int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, this.browseViewMode);
        setPageSizeContent(pageSize);
        setPageSizeSpaces(pageSize);
    }

    /**
     * @return the Set of NodeEventListeners registered against this bean
     */
    private Set<NodeEventListener> getNodeEventListeners() {
        if ((this.nodeEventListeners == null) || (Application.isDynamicConfig(FacesContext.getCurrentInstance()))) {
            Set<NodeEventListener> allNodeEventListeners = new HashSet<NodeEventListener>();

            if (Application.isDynamicConfig(FacesContext.getCurrentInstance())
                    && (this.nodeEventListeners != null)) {
                // for dynamic config, can add/remove node event listeners dynamically ...
                // however, in case anyone is using public methods (add/removeNodeEventListener)
                // we merge list here with list returned from the config
                allNodeEventListeners.addAll(this.nodeEventListeners);
            }

            FacesContext fc = FacesContext.getCurrentInstance();
            Config listenerConfig = Application.getConfigService(fc).getConfig("Node Event Listeners");
            if (listenerConfig != null) {
                ConfigElement listenerElement = listenerConfig.getConfigElement("node-event-listeners");
                if (listenerElement != null) {
                    for (ConfigElement child : listenerElement.getChildren()) {
                        if (child.getName().equals("listener")) {
                            // retrieved the JSF Managed Bean identified in the config
                            String listenerName = child.getValue().trim();
                            Object bean = FacesHelper.getManagedBean(fc, listenerName);
                            if (bean instanceof NodeEventListener) {
                                allNodeEventListeners.add((NodeEventListener) bean);
                            }
                        }
                    }
                }
            }

            if (Application.isDynamicConfig(FacesContext.getCurrentInstance())) {
                return allNodeEventListeners;
            } else {
                this.nodeEventListeners = allNodeEventListeners;
            }
        }
        return this.nodeEventListeners;
    }

    /**
     * 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
     */
    public 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<IBreadcrumbHandler> 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 < location.size(); i++) {
                IBreadcrumbHandler element = location.get(i);
                if (element instanceof IRepoBreadcrumbHandler) {
                    NodeRef nodeRef = ((IRepoBreadcrumbHandler) element).getNodeRef();
                    if (ref.equals(nodeRef) == true) {
                        // TODO: we should be able to do this - but the UIBreadcrumb component modifies
                        //       it's own internal value when clicked - then uses that from then on!
                        //       the other ops are using the same List object and modding it directly.
                        //List<IBreadcrumbHandler> newLocation = new ArrayList<IBreadcrumbHandler>(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 < count; n++) {
                            location.remove(i + 1);
                        }

                        foundNode = true;
                        break;
                    }
                }
            }

            // add new node to the end of the existing breadcrumb
            if (foundNode == false) {
                FacesContext context = FacesContext.getCurrentInstance();
                String breadcrumbMode = Application.getClientConfig(context).getBreadcrumbMode();

                if (ClientConfigElement.BREADCRUMB_LOCATION.equals(breadcrumbMode)) {
                    // if the breadcrumb is in "location" mode set the breadcrumb
                    // to the full path to the node

                    // TODO: check the end of the current breadcrumb, if the given
                    //       node is a child then we can shortcut the build of the
                    //       whole path.

                    Repository.setupBreadcrumbLocation(context, this.navigator, location, ref);
                } else {
                    // if the breadcrum is in "path" mode just add the given item to the end
                    String name = Repository.getNameForNode(this.getNodeService(), ref);
                    location.add(new BrowseBreadcrumbHandler(ref, name));
                }
            }
        } else {
            // special case to add first item to the location
            String name = Repository.getNameForNode(this.getNodeService(), ref);
            location.add(new BrowseBreadcrumbHandler(ref, name));
        }

        if (logger.isDebugEnabled())
            logger.debug("Updated breadcrumb: " + location);

        // set the current node Id ready for page refresh
        this.navigator.setCurrentNodeId(ref.getId());

        // set up the dispatch context for the navigation handler
        this.navigator.setupDispatchContext(new Node(ref));

        // inform any listeners that the current space has changed
        UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged();

        navigateBrowseScreen();
    }

    /**
     * Invalidate list component state after an action which changes the UI context
     */
    private void invalidateComponents() {
        if (logger.isDebugEnabled())
            logger.debug("Invalidating browse components...");

        // clear the value for the list components - will cause re-bind to it's data and refresh
        if (this.contentRichList != null) {
            this.contentRichList.setValue(null);
            if (this.navigator.getSearchContext() != null) {
                // clear the sorting mode so the search results are displayed in default 'score' order
                this.contentRichList.clearSort();
            }
        }
        if (this.spacesRichList != null) {
            this.spacesRichList.setValue(null);
            if (this.navigator.getSearchContext() != null) {
                // clear the sorting mode so the search results are displayed in default 'score' order
                this.spacesRichList.clearSort();
            }
        }

        // clear the storage of the last set of nodes
        this.containerNodes = null;
        this.contentNodes = null;
        this.parentContainerNodes = null;
    }

    /**
     * @return whether the current View ID is the "browse" screen
     */
    private boolean isViewCurrent() {
        return (FacesContext.getCurrentInstance().getViewRoot().getViewId().equals(BROWSE_VIEW_ID));
    }

    /**
     * Perform navigation to the browse screen if it is not already the current View
     */
    private void navigateBrowseScreen() {
        String outcome = null;

        if (isViewCurrent() == false) {
            outcome = "browse";
        }

        FacesContext fc = FacesContext.getCurrentInstance();
        fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome);
    }

    /**
     * Event handler used when a file is being deleted, checks that the node
     * does not have an associated working copy.
     * 
     * @param event The event
     */
    public void deleteFile(ActionEvent event) {
        setupContentAction(event);

        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();

        String ref = params.get("ref");
        if (ref != null && ref.length() > 0) {
            NodeRef nodeRef = new NodeRef(ref);

            NodeRef workingCopyNodeRef = getCheckOutCheckInService().getWorkingCopy(nodeRef);

            if (workingCopyNodeRef != null) {
                // if node has a working copy setup error message and return
                Utils.addErrorMessage(MessageFormat.format(
                        Application.getMessage(FacesContext.getCurrentInstance(),
                                MSG_CANNOT_DELETE_NODE_HAS_WORKING_COPY),
                        new Object[] { getNodeService().getProperty(nodeRef, ContentModel.PROP_NAME) }));
                return;
            }

            // if there isn't a working copy go to normal delete dialog
            boolean hasMultipleParents = false;
            boolean showDeleteAssocDialog = false;

            // get type of node being deleted
            Node node = this.getDocument();
            QName type = node.getType();
            TypeDefinition typeDef = this.dictionaryService.getType(type);

            // determine if the node being delete has multiple parents
            if (!type.equals(ContentModel.TYPE_MULTILINGUAL_CONTAINER)
                    && !node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)
                    && !node.hasAspect(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)
                    && !type.equals(ContentModel.TYPE_LINK)
                    && !this.dictionaryService.isSubClass(typeDef.getName(), ContentModel.TYPE_LINK)) {
                List<ChildAssociationRef> parents = this.nodeService.getParentAssocs(node.getNodeRef(),
                        ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                if (parents != null && parents.size() > 1) {
                    hasMultipleParents = true;
                }
            }

            // determine which delete dialog to display
            if (this.navigator.getSearchContext() == null && hasMultipleParents) {
                // if we are not in a search and the node has multiple parents
                // see if the current node has the primary parent association
                NodeRef parentSpace = this.navigator.getCurrentNode().getNodeRef();
                ChildAssociationRef assoc = this.nodeService.getPrimaryParent(node.getNodeRef());

                // show delete assoc dialog if the current space is not the primary parent for the node
                showDeleteAssocDialog = !parentSpace.equals(assoc.getParentRef());
            }

            // show the appropriate dialog
            FacesContext fc = FacesContext.getCurrentInstance();
            if (showDeleteAssocDialog) {
                fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteFileAssoc");
            } else {
                final Map<String, String> dialogParams = new HashMap<String, String>(1);
                dialogParams.put("hasMultipleParents", Boolean.toString(hasMultipleParents));
                Application.getDialogManager().setupParameters(dialogParams);
                fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteFile");
            }
        }
    }

    /**
     * Handles the deleteSpace action by deciding which delete dialog to display
     */
    public void deleteSpace(ActionEvent event) {
        setupDeleteAction(event);

        boolean hasMultipleParents = false;
        boolean showDeleteAssocDialog = false;

        // determine if the node being delete has multiple parents
        Node node = this.getActionSpace();
        List<ChildAssociationRef> parents = this.nodeService.getParentAssocs(node.getNodeRef(),
                ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
        if (parents != null && parents.size() > 1) {
            hasMultipleParents = true;
        }

        // determine which delete dialog to display
        if (this.navigator.getSearchContext() == null && hasMultipleParents) {
            // if we are not in a search and the node has multiple parents
            // see if the current node has the primary parent association
            NodeRef parentSpace = this.navigator.getCurrentNode().getNodeRef();
            ChildAssociationRef assoc = this.nodeService.getPrimaryParent(node.getNodeRef());

            // show delete assoc dialog if the current space is not the primary parent for the node
            showDeleteAssocDialog = !parentSpace.equals(assoc.getParentRef());
        }

        // show the appropriate dialog
        FacesContext fc = FacesContext.getCurrentInstance();
        if (showDeleteAssocDialog) {
            fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteSpaceAssoc");
        } else {
            final Map<String, String> dialogParams = new HashMap<String, String>(1);
            dialogParams.put("hasMultipleParents", Boolean.toString(hasMultipleParents));
            Application.getDialogManager().setupParameters(dialogParams);
            fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:deleteSpace");
        }
    }

    // ------------------------------------------------------------------------------
    // Inner classes

    /**
     * Class to handle breadcrumb interaction for Browse pages
     */
    private class BrowseBreadcrumbHandler implements IRepoBreadcrumbHandler {
        private static final long serialVersionUID = 3833183653173016630L;

        /**
         * Constructor
         *
         * @param nodeRef    The NodeRef for this browse navigation element
         * @param label      Element label
         */
        public BrowseBreadcrumbHandler(NodeRef nodeRef, String label) {
            this.label = label;
            this.nodeRef = nodeRef;
        }

        /**
         * @see java.lang.Object#toString()
         */
        public String toString() {
            return this.label;
        }

        /**
         * @see org.alfresco.web.ui.common.component.IBreadcrumbHandler#navigationOutcome(org.alfresco.web.ui.common.component.UIBreadcrumb)
         */
        @SuppressWarnings("unchecked")
        public String navigationOutcome(UIBreadcrumb breadcrumb) {
            // All browse breadcrumb element relate to a Node Id - when selected we
            // set the current node id
            navigator.setCurrentNodeId(this.nodeRef.getId());
            navigator.setLocation((List) breadcrumb.getValue());

            // setup the dispatch context
            navigator.setupDispatchContext(new Node(this.nodeRef));

            // inform any listeners that the current space has changed
            UIContextService.getInstance(FacesContext.getCurrentInstance()).spaceChanged();

            // return to browse page if required
            return (isViewCurrent() ? null : "browse");
        }

        public NodeRef getNodeRef() {
            return this.nodeRef;
        }

        private NodeRef nodeRef;
        private String label;
    }

    // ------------------------------------------------------------------------------
    // Private data

    private static Log logger = LogFactory.getLog(BrowseBean.class);

    /** Browse screen view ID */
    public static final String BROWSE_VIEW_ID = "/jsp/browse/browse.jsp";
    public static final String CATEGORY_VIEW_ID = "/jsp/browse/category-browse.jsp";

    /** Small icon default name */
    public static final String SPACE_SMALL_DEFAULT = "space_small";

    private static final String VIEWMODE_DASHBOARD = "dashboard";
    private static final String PAGE_NAME_BROWSE = "browse";

    /** I18N messages */
    private static final String MSG_DELETE_COMPANYROOT = "delete_companyroot_confirm";
    public static final String MSG_SEARCH_MINIMUM = "search_minimum";
    private static final String MSG_CANNOT_DELETE_NODE_HAS_WORKING_COPY = "cannot_delete_node_has_working_copy";

    /** The NodeService to be used by the bean */
    private transient NodeService nodeService;

    /** The CheckOutCheckInService to be used by the bean */
    private transient CheckOutCheckInService checkOutCheckInService;

    /** The SearchService to be used by the bean */
    private transient SearchService searchService;

    /** The LockService to be used by the bean */
    private transient LockService lockService;

    /** The NavigationBean bean reference */
    protected NavigationBean navigator;

    /** The UserPreferencesBean to be used by the bean */
    protected UserPreferencesBean userPreferencesBean;

    /** The DictionaryService bean reference */
    private transient DictionaryService dictionaryService;

    /** The file folder service */
    private transient FileFolderService fileFolderService;

    /** The Multilingual Content Service */
    private transient MultilingualContentService multilingualContentService;

    /** Views configuration object */
    protected ViewsConfigElement viewsConfig = null;

    /** Listeners for Node events */
    protected Set<NodeEventListener> nodeEventListeners = null;

    /** Collapsable Panel state */
    private Map<String, Boolean> panels = new HashMap<String, Boolean>(4, 1.0f);

    /** Component references */
    protected UIRichList spacesRichList;
    protected UIRichList contentRichList;
    private UIStatusMessage statusMessage;

    /** Transient lists of container and content nodes for display */
    protected List<Node> containerNodes = null;
    protected List<Node> contentNodes = null;
    protected List<Node> parentContainerNodes = null;

    /** The current space and it's properties - if any */
    protected Node actionSpace;

    /** The current document */
    protected 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 sizes */
    private int pageSizeSpaces;
    private int pageSizeContent;
    private String pageSizeSpacesStr;
    private String pageSizeContentStr;

    /** True if current space has a dashboard (template) view available */
    private boolean dashboardView;

    private boolean externalForceRefresh = false;
}