org.alfresco.web.bean.wcm.AVMBrowseBean.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * 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/>.
 */
package org.alfresco.web.bean.wcm;

import java.io.FileNotFoundException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIParameter;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import javax.transaction.UserTransaction;

import org.alfresco.config.JNDIConstants;
import org.alfresco.model.ContentModel;
import org.alfresco.model.WCMAppModel;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.avm.AVMNodeType;
import org.alfresco.repo.web.scripts.FileTypeImageUtils;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avmsync.AVMDifference;
import org.alfresco.service.cmr.avmsync.AVMSyncService;
import org.alfresco.service.cmr.repository.FileTypeImageSize;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateImageResolver;
import org.alfresco.service.cmr.repository.TemplateService;
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.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.wcm.asset.AssetInfo;
import org.alfresco.wcm.sandbox.SandboxInfo;
import org.alfresco.wcm.sandbox.SandboxService;
import org.alfresco.wcm.util.WCMUtil;
import org.alfresco.wcm.webproject.WebProjectInfo;
import org.alfresco.wcm.webproject.WebProjectService;
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.BrowseBean;
import org.alfresco.web.bean.NavigationBean;
import org.alfresco.web.bean.repository.Node;
import org.alfresco.web.bean.repository.Repository;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.bean.search.SearchContext;
import org.alfresco.web.forms.FormInstanceData;
import org.alfresco.web.forms.FormNotFoundException;
import org.alfresco.web.forms.FormsService;
import org.alfresco.web.forms.Rendition;
import org.alfresco.web.ui.common.Utils;
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.data.UIRichList;
import org.alfresco.web.ui.wcm.WebResources;
import org.alfresco.web.ui.wcm.component.UISandboxSnapshots;
import org.alfresco.web.ui.wcm.component.UIUserSandboxes;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.ConfigElement;
import org.springframework.extensions.config.ConfigService;

/**
 * Bean backing up the AVM specific browse screens
 * 
 * @author Kevin Roast
 */
public class AVMBrowseBean implements IContextListener {
    private static final long serialVersionUID = -2310105113473561134L;

    public static final String BEAN_NAME = "AVMBrowseBean";

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

    public static final String REQUEST_BEEN_DEPLOYED_RESULT = "_alfBeenDeployedResult";

    private static final String MSG_REVERT_SUCCESS = "revert_success";
    private static final String MSG_REVERT_SANDBOX = "revert_sandbox_success";
    private static final String MSG_SANDBOXTITLE = "sandbox_title";
    private static final String MSG_SANDBOXSTAGING = "sandbox_staging";
    private static final String MSG_CREATED_ON = "store_created_on";
    private static final String MSG_CREATED_BY = "store_created_by";
    private static final String MSG_WORKING_USERS = "store_working_users";
    private static final String MSG_SEARCH_FORM_CONTENT = "search_form_content";
    private static final String MSG_TARGET_IS_DELETED = "target_is_deleted";

    /** Component id the status messages are tied too */
    static final String COMPONENT_SANDBOXESPANEL = "sandboxes-panel";

    /** Top-level JSF form ID */
    static final String FORM_ID = "website";

    /** Snapshot date filter selection */
    private String snapshotDateFilter = UISandboxSnapshots.FILTER_DATE_TODAY;

    /** Current sandbox store context for actions and sandbox view */
    private String sandbox;

    /** Current username context for actions and sandbox view */
    private String username;

    /** Current webapp context for actions and sandbox view */
    private String webapp;

    /** List of top-level webapp directories for the current web project */
    private List<SelectItem> webapps;

    /** Sandbox title message */
    private String sandboxTitle = null;

    /** Current AVM path and node representing the current path */
    private String currentPath = null;
    private AVMNode currentPathNode = null;

    /** flag to indicate that all items in the sandbox are involved in the current action */
    private boolean allItemsAction = false;

    /** list of the deployment monitor ids currently executing */
    private List<String> deploymentMonitorIds = new ArrayList<String>();

    /** List of expired paths to submit */
    private List<AVMNodeDescriptor> nodesForSubmit = Collections.<AVMNodeDescriptor>emptyList();

    /* component references */
    private UIRichList foldersRichList;
    private UIRichList filesRichList;
    private UIUserSandboxes userSandboxes;

    /** transient lists of files/folders for a directory */
    private List<Map> files = null;
    private List<Map> folders = null;

    /** Current AVM Node action context */
    private AVMNode avmNode = null;

    /** The last displayed website node id */
    private String lastWebsiteId = null;

    /** The current webProject */
    private WebProject webProject = null;

    /** breadcrumb location */
    private List<IBreadcrumbHandler> location = null;

    /** Show all user sandboxes flag */
    private boolean showAllSandboxes = false;

    /** The current view page sizes */
    private int pageSizeFolders;
    private int pageSizeFiles;
    private String pageSizeFoldersStr;
    private String pageSizeFilesStr;

    /** search query string */
    private String websiteQuery;

    private SearchContext searchContext = null;

    private String searchOrigin;

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

    /** The WorkflowService bean reference. */
    transient private WorkflowService workflowService;

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

    /** WebProjectService bean reference */
    transient protected WebProjectService wpService;

    /** SandboxService bean reference */
    transient protected SandboxService sbService;

    /** AVM service bean reference */
    transient protected AVMService avmService;

    /** AVM sync service bean reference */
    transient protected AVMSyncService avmSyncService;

    /** Action service bean reference */
    transient protected ActionService actionService;

    /** The FormsService reference */
    transient protected FormsService formsService;

    /** The SearchService reference */
    transient private SearchService searchService;

    /** The PermissionService reference */
    transient protected PermissionService permissionService;

    /**
     * Default Constructor
     */
    public AVMBrowseBean() {
        UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this);

        initFromClientConfig();
    }

    // ------------------------------------------------------------------------------
    // Bean property getters and setters 

    /**
     * @param wpService The WebProjectService to set.
     */
    public void setWebProjectService(WebProjectService wpService) {
        this.wpService = wpService;
    }

    protected WebProjectService getWebProjectService() {
        if (wpService == null) {
            wpService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getWebProjectService();
        }
        return wpService;
    }

    /**
     * @param sbService The SandboxService to set.
     */
    public void setSandboxService(SandboxService sbService) {
        this.sbService = sbService;
    }

    protected SandboxService getSandboxService() {
        if (sbService == null) {
            sbService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getSandboxService();
        }
        return sbService;
    }

    /**
     * @param avmService The AVMService to set.
     */
    public void setAvmService(AVMService avmService) {
        this.avmService = avmService;
    }

    protected AVMService getAvmService() {
        if (avmService == null) {
            avmService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getAVMService();
        }
        return avmService;
    }

    /**
     * @param avmSyncService   The AVMSyncService to set.
     */
    public void setAvmSyncService(AVMSyncService avmSyncService) {
        this.avmSyncService = avmSyncService;
    }

    protected AVMSyncService getAvmSyncService() {
        if (avmSyncService == null) {
            avmSyncService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getAVMSyncService();
        }
        return avmSyncService;
    }

    /**
     * @param nodeService The NodeService to set.
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * Getter used by the Inline Edit XML JSP
     * 
     * @return The NodeService
     */
    public NodeService getNodeService() {
        if (nodeService == null) {
            nodeService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService();
        }
        return nodeService;
    }

    /**
     * Set the workflow service
     * @param service The workflow service instance.
     */
    public void setWorkflowService(WorkflowService service) {
        workflowService = service;
    }

    protected WorkflowService getWorkflowService() {
        if (workflowService == null) {
            workflowService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getWorkflowService();
        }
        return workflowService;
    }

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

    /**
     * @return searchService
     */
    public SearchService getSearchService() {
        //check for null for cluster environment
        if (searchService == null) {
            searchService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getSearchService();
        }
        return searchService;
    }

    /**
     * @param permissionService The PermissionService to set.
     */
    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    protected PermissionService getPermissionService() {
        if (permissionService == null) {
            permissionService = Repository.getServiceRegistry(FacesContext.getCurrentInstance())
                    .getPermissionService();
        }
        return permissionService;
    }

    /**
     * @param navigator The NavigationBean to set.
     */
    public void setNavigationBean(NavigationBean navigator) {
        this.navigator = navigator;
    }

    /**
     * @param actionService The actionService to set.
     */
    public void setActionService(ActionService actionService) {
        this.actionService = actionService;
    }

    protected ActionService getActionService() {
        if (actionService == null) {
            actionService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getActionService();
        }
        return actionService;
    }

    /**
     * @param formsService    The FormsService to set.
     */
    public void setFormsService(final FormsService formsService) {
        this.formsService = formsService;
    }

    protected FormsService getFormsService() {
        if (formsService == null) {
            formsService = (FormsService) FacesHelper.getManagedBean(FacesContext.getCurrentInstance(),
                    "FormsService");
        }
        return formsService;
    }

    public int getPageSizeFiles() {
        return this.pageSizeFiles;
    }

    public void setPageSizeFiles(int pageSizeContent) {
        this.pageSizeFiles = pageSizeContent;
        this.pageSizeFilesStr = Integer.toString(pageSizeContent);
    }

    public int getPageSizeFolders() {
        return this.pageSizeFolders;
    }

    public void setPageSizeFolders(int pageSizeSpaces) {
        this.pageSizeFolders = pageSizeSpaces;
        this.pageSizeFoldersStr = Integer.toString(pageSizeSpaces);
    }

    public String getPageSizeFilesStr() {
        return this.pageSizeFilesStr;
    }

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

    public String getPageSizeFoldersStr() {
        return this.pageSizeFoldersStr;
    }

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

    /**
     * Summary text for the staging store:
     *    Created On: xx/yy/zz
     *    Created By: username
     *    There are N user(s) working on this website.
     * 
     * @return summary text
     */
    public String getStagingSummary() {
        StringBuilder summary = new StringBuilder(128);
        FacesContext fc = FacesContext.getCurrentInstance();
        ResourceBundle msg = Application.getBundle(fc);

        NodeRef wpNodeRef = getWebsite().getNodeRef();
        WebProjectInfo wpInfo = getWebProjectService().getWebProject(wpNodeRef);
        SandboxInfo sbInfo = getSandboxService().getStagingSandbox(wpInfo.getStoreId());

        if (sbInfo != null) {
            summary.append(msg.getString(MSG_CREATED_ON)).append(": ")
                    .append(Utils.getDateFormat(fc).format(sbInfo.getCreatedDate())).append("<p>");
            summary.append(msg.getString(MSG_CREATED_BY)).append(": ").append(sbInfo.getCreator()).append("<p>");
            final int numUsers = getWebProjectService().getWebUserCount(wpNodeRef);
            summary.append(MessageFormat.format(msg.getString(MSG_WORKING_USERS), numUsers));
        }

        // reset the current path so the context for the Modified File list actions is cleared
        this.currentPath = null;

        return summary.toString();
    }

    /**
     * @return the current staging store name
     */
    public String getStagingStore() {
        return this.getWebProject().getStagingStore();
    }

    /**
     * @return Preview URL for the current Staging store and current webapp
     */
    public String getStagingPreviewUrl() {
        return (AVMUtil.getPreviewURI(getStagingStore(),
                '/' + JNDIConstants.DIR_DEFAULT_WWW + '/' + JNDIConstants.DIR_DEFAULT_APPBASE + '/' + getWebapp()));
    }

    /**
     * @return Preview URL for the current User Sandbox store and current webapp
     */
    public String getSandboxPreviewUrl() {
        return (AVMUtil.getPreviewURI(getSandbox(),
                '/' + JNDIConstants.DIR_DEFAULT_WWW + '/' + JNDIConstants.DIR_DEFAULT_APPBASE + '/' + getWebapp()));
    }

    /**
     * @param foldersRichList      The foldersRichList to set.
     */
    public void setFoldersRichList(UIRichList foldersRichList) {
        this.foldersRichList = foldersRichList;
    }

    /**
     * @return Returns the foldersRichList.
     */
    public UIRichList getFoldersRichList() {
        return this.foldersRichList;
    }

    /**
     * @return Returns the filesRichList.
     */
    public UIRichList getFilesRichList() {
        return this.filesRichList;
    }

    /**
     * @param filesRichList       The filesRichList to set.
     */
    public void setFilesRichList(UIRichList filesRichList) {
        this.filesRichList = filesRichList;
    }

    /**
     * @return Returns the userSandboxes.
     */
    public UIUserSandboxes getUserSandboxes() {
        return this.userSandboxes;
    }

    /**
     * @param userSandboxes       The userSandboxes to set.
     */
    public void setUserSandboxes(UIUserSandboxes userSandboxes) {
        this.userSandboxes = userSandboxes;
    }

    /**
     * @return Returns the current sandbox context.
     */
    public String getSandbox() {
        return this.sandbox;
    }

    /**
     * @param sandbox    The sandbox to set.
     */
    public void setSandbox(String sandbox) {
        this.sandbox = sandbox;
    }

    /**
     * @return Returns the current username context.
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * @param username   The username to set.
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * @return current webapp context
     */
    public String getWebapp() {
        if (this.webapp == null) {
            // TODO - temporary, should only be called for WCM forms (not ECM forms)
            Node wpNode = getWebsite();
            if (wpNode != null) {
                WebProjectInfo wpInfo = getWebProjectService().getWebProject(wpNode.getNodeRef());
                if (wpInfo != null) {
                    this.webapp = wpInfo.getDefaultWebApp();
                }
            }
        }
        return this.webapp;
    }

    /**
     * @param webapp  Webapp folder context
     */
    public void setWebapp(String webapp) {
        this.webapp = webapp;
    }

    /**
     * @return Returns the list of deployment monitor ids currently executing
     */
    public List<String> getDeploymentMonitorIds() {
        return this.deploymentMonitorIds;
    }

    /**
     * @param deploymentMonitorIds Sets the list of deployment monitor ids
     */
    public void setDeploymentMonitorIds(List<String> deploymentMonitorIds) {
        this.deploymentMonitorIds = deploymentMonitorIds;
    }

    public List<AVMNodeDescriptor> getNodesForSubmit() {
        return this.nodesForSubmit;
    }

    public void setNodesForSubmit(final List<AVMNodeDescriptor> nodesForSubmit) {
        this.nodesForSubmit = nodesForSubmit;
    }

    /**
     * @return list of available root webapp folders for this Web project
     */
    public List<SelectItem> getWebapps() {
        if (this.webapps == null) {
            List<String> webAppNames = getWebProjectService().listWebApps(getWebsite().getNodeRef());
            List<SelectItem> webAppItems = new ArrayList<SelectItem>(webAppNames.size());
            for (String webAppName : webAppNames) {
                webAppItems.add(new SelectItem(webAppName, webAppName));
            }
            this.webapps = webAppItems;
        }
        return this.webapps;
    }

    /**
     * @return count of the root webapps in the current web project
     */
    public int getWebappsSize() {
        return getWebapps().size();
    }

    /**
     * @return Returns the sandboxTitle.
     */
    public String getSandboxTitle() {
        if (this.sandboxTitle == null) {
            String forUser = username;
            if (forUser == null) {
                forUser = Application.getMessage(FacesContext.getCurrentInstance(), MSG_SANDBOXSTAGING);
            }
            this.sandboxTitle = MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), MSG_SANDBOXTITLE),
                    getWebsite().getName(), forUser);
        }
        return this.sandboxTitle;
    }

    /**
     * @param sandboxTitle The sandboxTitle to set.
     */
    public void setSandboxTitle(String sandboxTitle) {
        this.sandboxTitle = sandboxTitle;
    }

    /**
     * @return Returns the Snapshot Date Filter.
     */
    public String getSnapshotDateFilter() {
        return this.snapshotDateFilter;
    }

    /**
     * @param snapshotDateFilter The Snapshot Date Filter to set.
     */
    public void setSnapshotDateFilter(String snapshotDateFilter) {
        this.snapshotDateFilter = snapshotDateFilter;
    }

    /**
     * @return the website search query string
     */
    public String getWebsiteQuery() {
        return this.websiteQuery;
    }

    /**
     * @param websiteQuery   The website search query string
     */
    public void setWebsiteQuery(String websiteQuery) {
        this.websiteQuery = websiteQuery;
    }

    /**
     * @return icon image for the appropriate sandbox type
     */
    public String getIcon() {
        return this.username == null ? WebResources.IMAGE_SANDBOX_32 : WebResources.IMAGE_USERSANDBOX_32;
    }

    /**
     * @return website node the view is currently within
     */
    public Node getWebsite() {
        // check to see if the website we are browsing has changed since the last time
        final Node currentNode = this.navigator.getCurrentNode();
        if (!this.navigator.getCurrentNodeId().equals(this.lastWebsiteId)
                || !WCMAppModel.TYPE_AVMWEBFOLDER.equals(currentNode.getType())) {
            // clear context when we are browsing a new website
            this.lastWebsiteId = this.navigator.getCurrentNodeId();
            this.webapp = null;
            this.webapps = null;
            this.webProject = null;
        }
        return WCMAppModel.TYPE_AVMWEBFOLDER.equals(currentNode.getType()) ? currentNode : null;
    }

    /**
     * @return the web project the view is currently within
     */
    public WebProject getWebProject() {
        Node website = this.getWebsite();
        if (this.webProject == null && website != null) {
            this.webProject = new WebProject(website.getNodeRef());
        }
        return this.webProject;
    }

    /**
     * @return Returns the current AVM node action context.
     */
    public AVMNode getAvmActionNode() {
        return this.avmNode;
    }

    /**
     * @param avmNode       The AVM node action context to set.
     */
    public void setAvmActionNode(AVMNode avmNode) {
        this.avmNode = avmNode;
    }

    /**
     * @param avmRef        The AVMNodeDescriptor action context to set.
     */
    public void setAVMActionNodeDescriptor(AVMNodeDescriptor avmRef) {
        if (avmRef == null) {
            throw new NullPointerException();
        }
        AVMNode avmNode = new AVMNode(avmRef);
        this.avmNode = avmNode;
    }

    /**
     * @return the internal AVM path to the current folder for browsing
     */
    public String getCurrentPath() {
        if (this.currentPath == null) {
            // TODO - temporary, should only be called for WCM forms (not ECM forms)
            String webApp = getWebapp();
            if (webApp != null) {
                this.currentPath = AVMUtil.buildStoreWebappPath(getSandbox(), webApp);
            }
        }
        return this.currentPath;
    }

    /**
     * @param path       the internal AVM path to the current folder for browsing
     */
    public void setCurrentPath(String path) {
        this.currentPath = path;
        if (path == null) {
            // clear dependant objects (recreated when the path is reinitialised) 
            this.currentPathNode = null;
        }

        // clear the search context as we have navigated to a folder path
        this.searchContext = null;

        // update UI state ready for screen refresh
        UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
    }

    /**
     * @return the AVMNode that represents the current browsing path
     */
    public AVMNode getCurrentPathNode() {
        if (this.currentPathNode == null) {
            AVMNodeDescriptor node = getAvmService().lookup(-1, getCurrentPath(), true);
            this.currentPathNode = new AVMNode(node);
        }
        return this.currentPathNode;
    }

    /**
     * @return Breadcrumb location list
     */
    public List<IBreadcrumbHandler> getLocation() {
        if (this.location == null) {
            List<IBreadcrumbHandler> loc = new ArrayList<IBreadcrumbHandler>(8);
            loc.add(new AVMBreadcrumbHandler(getCurrentPath()));

            this.location = loc;
        }
        return this.location;
    }

    /**
     * @param location Breadcrumb location list
     */
    public void setLocation(List<IBreadcrumbHandler> location) {
        this.location = location;
    }

    /**
    * @return true if the current node has a custom view available
    */
    public boolean getHasCustomView() {
        return getHasWebscriptView() || getHasTemplateView();
    }

    /**
     * @return true if the current node has a Template based custom view available
     */
    public boolean getHasTemplateView() {
        AVMNode node = getCurrentPathNode();
        if (node.hasAspect(ContentModel.ASPECT_TEMPLATABLE)) {
            NodeRef templateRef = (NodeRef) node.getProperties().get(ContentModel.PROP_TEMPLATE);
            return (templateRef != null && this.getNodeService().exists(templateRef) && getPermissionService()
                    .hasPermission(templateRef, PermissionService.READ) == AccessStatus.ALLOWED);
        }
        return false;
    }

    /**
     * @return true if the current node has a Webscript based custom view available
     */
    public boolean getHasWebscriptView() {
        AVMNode node = getCurrentPathNode();
        if (node.hasAspect(ContentModel.ASPECT_WEBSCRIPTABLE)) {
            return (node.getProperties().get(ContentModel.PROP_WEBSCRIPT) != null);
        }
        return false;
    }

    /**
     * @return the NodeRef.toString() for the current node Template custom view if it has one 
     */
    public String getCurrentNodeTemplate() {
        NodeRef ref = (NodeRef) getCurrentPathNode().getProperties().get(ContentModel.PROP_TEMPLATE);
        return ref != null ? ref.toString() : null;
    }

    /**
     * @return the service url for the current node Webscript custom view if it has one 
     */
    public String getCurrentNodeWebscript() {
        return (String) getCurrentPathNode().getProperties().get(ContentModel.PROP_WEBSCRIPT);
    }

    /**
     * Returns a model for use by a template on a space Dashboard page.
     * 
     * @return model containing current current space info.
     */
    @SuppressWarnings("unchecked")
    public Map getTemplateModel() {
        HashMap model = new HashMap(4, 1.0f);

        model.put("space", getCurrentPathNode().getNodeRef());
        model.put("path", getCurrentPathNode().getPath());
        model.put(TemplateService.KEY_IMAGE_RESOLVER, new TemplateImageResolver() {
            public String resolveImagePathForName(String filename, FileTypeImageSize size) {
                return FileTypeImageUtils.getFileTypeImage(FacesContext.getCurrentInstance(), filename, size);
            }
        });

        return model;
    }

    public Map getCustomWebscriptContext() {
        HashMap model = new HashMap(2, 1.0f);
        model.put("path", getCurrentPathNode().getPath());
        return model;
    }

    /**
     * @return true if the current user has the manager role in the current website
     */
    public boolean getIsManagerRole() {
        Node wpNode = getWebsite();
        if (wpNode != null && nodeService.exists(wpNode.getNodeRef())) {
            return getWebProjectService().isContentManager(wpNode.getNodeRef());
        }
        return false;
    }

    public boolean getIsManagerOrPublisherRole() {
        Node wpNode = getWebsite();
        if (wpNode != null && nodeService.exists(wpNode.getNodeRef())) {
            User user = Application.getCurrentUser(FacesContext.getCurrentInstance());
            String userRole = getWebProjectService().getWebUserRole(wpNode.getNodeRef(), user.getUserName());
            return (WCMUtil.ROLE_CONTENT_MANAGER.equals(userRole)
                    || WCMUtil.ROLE_CONTENT_PUBLISHER.equals(userRole));
        }
        return false;
    }

    /**
     * @return true to show all sandboxes visible to this user, false to only show the current user sandbox
     */
    public boolean getShowAllSandboxes() {
        return this.showAllSandboxes;
    }

    /**
     * @param value  true to show all sandboxes visible to this user, false to only show the current user sandbox
     */
    public void setShowAllSandboxes(boolean value) {
        this.showAllSandboxes = value;
    }

    /**
     * @return true if the website has had a deployment attempt
     */
    @SuppressWarnings("unchecked")
    public boolean getHasDeployBeenAttempted() {
        // NOTE: This method is called a lot as it is referenced as a value binding
        //       expression in a 'rendered' attribute, we therefore cache the result
        //       on a per request basis

        Boolean result = null;

        FacesContext context = FacesContext.getCurrentInstance();
        Map request = context.getExternalContext().getRequestMap();
        if (request.get(REQUEST_BEEN_DEPLOYED_RESULT) == null) {
            if (!nodeService.exists(this.getWebsite().getNodeRef())) {
                result = false;
            } else {
                // see if there are any deployment attempts for the staging area
                NodeRef webProjectRef = this.getWebsite().getNodeRef();
                String store = (String) getNodeService().getProperty(webProjectRef, WCMAppModel.PROP_AVMSTORE);
                List<NodeRef> deployAttempts = DeploymentUtil.findDeploymentAttempts(store);

                // add a placeholder object in the request so we don't evaluate this again for this request
                result = new Boolean(deployAttempts != null && deployAttempts.size() > 0);
            }
            request.put(REQUEST_BEEN_DEPLOYED_RESULT, result);
        } else {
            result = (Boolean) request.get(REQUEST_BEEN_DEPLOYED_RESULT);
        }

        return result.booleanValue();
    }

    /**
     * @return the Search Context object for the current website project
     */
    public SearchContext getSearchContext() {
        return this.searchContext;
    }

    /**
     * @return Map of avm node objects representing the folders with the current website space
     */
    public List<Map> getFolders() {
        if (this.folders == null) {
            if (this.searchContext == null) {
                buildDirectoryNodes();
            } else {
                buildSearchNodes();
            }
        }
        return this.folders;
    }

    /**
     * @return Map of avm node objects representing the files with the current website space
     */
    public List<Map> getFiles() {
        if (this.files == null) {
            if (this.searchContext == null) {
                buildDirectoryNodes();
            } else {
                buildSearchNodes();
            }
        }
        return this.files;
    }

    /**
     * Build the lists of files and folders within the current browsing path in a website space
     */
    private void buildDirectoryNodes() {
        UserTransaction tx = null;
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            tx = Repository.getUserTransaction(context, true);
            tx.begin();

            Map<String, AVMNodeDescriptor> nodes = getAvmService().getDirectoryListing(-1, getCurrentPath());
            this.files = new ArrayList<Map>(nodes.size());
            this.folders = new ArrayList<Map>(nodes.size());
            for (String name : nodes.keySet()) {
                AVMNodeDescriptor avmRef = nodes.get(name);

                // build and add the client representation of the AVM node
                addAVMNodeResult(avmRef);
            }

            // commit the transaction
            tx.commit();
        } catch (Throwable err) {
            Utils.addErrorMessage(MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC),
                    err.getMessage()), err);
            this.folders = Collections.<Map>emptyList();
            this.files = Collections.<Map>emptyList();
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        }
    }

    /**
     * Build the lists of files and folders from the current search context in a website space
     */
    private void buildSearchNodes() {
        String query = this.searchContext.buildQuery(getMinimumSearchLength());
        if (query == null) {
            this.folders = Collections.<Map>emptyList();
            this.files = Collections.<Map>emptyList();
            return;
        }

        UserTransaction tx = null;
        ResultSet results = null;
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            tx = Repository.getUserTransaction(context, true);
            tx.begin();

            // build up the search parameters
            SearchParameters sp = new SearchParameters();
            sp.setLanguage(SearchService.LANGUAGE_LUCENE);
            sp.setQuery(query);
            // add the Staging Store for this website - it is the only searchable store for now
            sp.addStore(new StoreRef(StoreRef.PROTOCOL_AVM, getStagingStore()));

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

            results = getSearchService().query(sp);

            if (logger.isDebugEnabled()) {
                logger.debug("Search results returned: " + results.length());
            }

            // filter hidden folders above the web app
            boolean isStagingStore = getIsStagingStore();
            int sandboxPathLength = AVMUtil.getSandboxPath(getCurrentPath()).length();

            this.files = new ArrayList<Map>(results.length());
            this.folders = new ArrayList<Map>(results.length());
            for (ResultSetRow row : results) {
                NodeRef nodeRef = row.getNodeRef();

                // Modify the path to point to the current user sandbox - this change is performed so
                // that any action permission evaluators will correctly resolve for the current user.
                // Therefore deleted node will be filtered out by the lookup() call, but some text based
                // results may be incorrect - however a note is provided in the search UI to indicate this.
                String path = AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond();
                if (isStagingStore == false) {
                    path = getSandbox() + ':' + AVMUtil.getStoreRelativePath(path);
                }
                if (path.length() > sandboxPathLength) {
                    AVMNodeDescriptor avmRef = getAvmService().lookup(-1, path);
                    if (avmRef != null) {
                        AVMNode node = addAVMNodeResult(avmRef);

                        // add extra properties for search results lists
                        node.addPropertyResolver("displayPath", AVMNode.RESOLVER_DISPLAY_PATH);
                        node.addPropertyResolver("parentPath", AVMNode.RESOLVER_PARENT_PATH);
                    }
                }
            }

            // commit the transaction
            tx.commit();
        } catch (Throwable err) {
            Utils.addErrorMessage(MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC),
                    err.getMessage()), err);
            this.folders = Collections.<Map>emptyList();
            this.files = Collections.<Map>emptyList();
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        } finally {
            if (results != null) {
                results.close();
            }
        }
    }

    /**
     * Add an AVM node result to the list of files/folders to display. Applies various
     * client pseudo properties resolver objects used for list display columns.
     */
    private AVMNode addAVMNodeResult(AVMNodeDescriptor avmRef) {
        // build the client representation of the AVM node
        AVMNode node = new AVMNode(avmRef);

        // properties specific to folders or files
        if (avmRef.isDirectory()) {
            node.getProperties().put("smallIcon", BrowseBean.SPACE_SMALL_DEFAULT);

            String type = "";
            if (avmRef.getType() == AVMNodeType.LAYERED_DIRECTORY && avmRef.isPrimary()) {
                if ((getAvmService().lookup(avmRef.getIndirectionVersion(), avmRef.getIndirection()) != null)
                        || (avmRef.getOpacity())) {
                    type = Application.getMessage(FacesContext.getCurrentInstance(), "shared_folder");
                } else {
                    type = Application.getMessage(FacesContext.getCurrentInstance(), "stale_shared_folder");
                }
            } else {
                type = Application.getMessage(FacesContext.getCurrentInstance(), "folder");
            }
            node.getProperties().put("folderType", type);

            this.folders.add(node);
        } else {
            String type = "";
            if (avmRef.isLayeredFile()) {
                if (getAvmService().lookup(avmRef.getIndirectionVersion(), avmRef.getIndirection()) != null) {
                    type = Application.getMessage(FacesContext.getCurrentInstance(), "shared_file");
                } else {
                    type = Application.getMessage(FacesContext.getCurrentInstance(), "stale_shared_file");
                }
            } else {
                type = Application.getMessage(FacesContext.getCurrentInstance(), "file");
            }

            node.getProperties().put("fileType", type);
            node.getProperties().put("fileType16", FileTypeImageUtils.getFileTypeImage(avmRef.getName(), true));
            node.getProperties().put("url", DownloadContentServlet
                    .generateBrowserURL(AVMNodeConverter.ToNodeRef(-1, avmRef.getPath()), avmRef.getName()));

            this.files.add(node);
        }

        // common properties
        node.addPropertyResolver("previewUrl", AVMNode.RESOLVER_PREVIEW_URL);

        return node;
    }

    /**
     * Return the list of selected items for the current user sandbox view
     * 
     * @return List of AVMNodeDescriptor objects representing selected items
     */
    public List<AVMNodeDescriptor> getSelectedSandboxItems() {
        return this.userSandboxes.getSelectedNodes(this.username);
    }

    /**
     * @return true if a special All Items action has been initialised
     */
    public boolean getAllItemsAction() {
        return this.allItemsAction;
    }

    /**
     * @return true if the current sandbox is a Staging store, false otherwise
     */
    public boolean getIsStagingStore() {
        return AVMUtil.isMainStore(this.sandbox);
    }

    // ------------------------------------------------------------------------------
    // Action event handlers

    /**
     * Update the UI after a folder click action in the website browsing screens
     */
    public void clickFolder(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        String path = params.get("id");
        AVMNodeDescriptor avmNode = getAvmService().lookup(-1, path);

        if (avmNode.isLayeredDirectory() && avmNode.isPrimary()
                && (getAvmService().lookup(avmNode.getIndirectionVersion(), avmNode.getIndirection()) == null)
                && (!avmNode.getOpacity()) && getAvmService().getDirectoryListingDirect(avmNode, false).isEmpty()) {
            String pattern = Application.getMessage(FacesContext.getCurrentInstance(), MSG_TARGET_IS_DELETED);
            String folderName = path.substring(path.lastIndexOf("/") + 1);
            Utils.addErrorMessage(MessageFormat.format(pattern, folderName));
        } else {
            updateUILocation(path);
        }
    }

    /**
     * Action handler called when the Snapshot Date filter is changed by the user
     */
    public void snapshotDateFilterChanged(ActionEvent event) {
        UIModeList filterComponent = (UIModeList) event.getComponent();
        setSnapshotDateFilter(filterComponent.getValue().toString());
    }

    /**
     * Setup the context for a sandbox browse action
     */
    public void setupSandboxAction(final ActionEvent event) {
        final UIActionLink link = (UIActionLink) event.getComponent();
        final Map<String, String> params = link.getParameterMap();
        this.setupSandboxAction(params.get("store"), params.get("username"));
    }

    /**
     * Setup the context for a sandbox browse action
     * 
     * @param store The store name for the action
     * @param username The authority pertinent to the action (null for staging store actions) 
     */
    public void setupSandboxAction(String store, String username) {
        this.setupSandboxActionImpl(store, null, username, true);
    }

    /**
     * Setup the context for a sandbox browse action
     * 
     * @param store      The store name for the action
     * @param username   The authority pertinent to the action (null for staging store actions)
     * @param reset      True to reset the current path and AVM action node context
     */
    private void setupSandboxActionImpl(final String store, final String webapp, final String username,
            final boolean reset) {
        // can be null if it's the staging store - i.e. not a user specific store
        setUsername(username);

        // the store can be either a user store or the staging store if this is null
        // get the staging store from the current website node
        this.setSandbox(store != null ? store : this.getStagingStore());

        if (webapp != null) {
            this.setWebapp(webapp);
        }

        // update UI state ready for return to the previous screen
        if (reset == true) {
            this.sandboxTitle = null;
            this.location = null;
            setCurrentPath(null);
            setAvmActionNode(null);
            this.allItemsAction = false;
            this.searchOrigin = null;
        }

        this.websiteQuery = null;
    }

    /**
     * Action event called by all actions that need to setup a Content node context on the 
     * before an action page/wizard is called. The context will be an AVMNodeDescriptor in
     * setAVMNode() which can be retrieved on action pages via getAVMNode().
     * 
     * @param event   ActionEvent
     */
    public void setupContentAction(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        this.setupContentAction(params.get("id"), true);
    }

    /*package*/ void setupContentAction(String path, boolean refresh) {
        if (logger.isDebugEnabled()) {
            logger.debug("Setup content action for path: " + path);
        }

        if (path == null || path.length() == 0) {
            setAvmActionNode(null);
        } else {
            // calculate username and store name from specified path
            String storeName = AVMUtil.getStoreName(path);
            String storeId = AVMUtil.getStoreId(storeName);
            String webapp = AVMUtil.getWebapp(path);
            String username = AVMUtil.getUserName(storeName);
            if (username == null) {
                storeName = (AVMUtil.isPreviewStore(storeName) ? AVMUtil.buildStagingPreviewStoreName(storeId)
                        : AVMUtil.buildStagingStoreName(storeId));
                this.setupSandboxActionImpl(storeName, webapp, null, false);
            } else {
                storeName = (AVMUtil.isPreviewStore(storeName)
                        ? AVMUtil.buildUserPreviewStoreName(storeId, username)
                        : AVMUtil.buildUserMainStoreName(storeId, username));
                this.setupSandboxActionImpl(storeName, webapp, username, false);
            }

            if (this.webProject == null) {
                this.webProject = new WebProject(path);
            }

            // setup the action node
            AVMNodeDescriptor avmNodeDesc = getAvmService().lookup(-1, path, true);
            this.setAVMActionNodeDescriptor(avmNodeDesc);
        }

        // update UI state ready for return after dialog close
        if (refresh) {
            UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans();
        }
    }

    /**
     * Action handler called to calculate which editing screen to display based on the mimetype
     * of a document. If appropriate, the in-line editing screen will be shown.
     */
    public void setupEditAction(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        this.setupEditAction(params.get("id"));
    }

    /**
     * Action handler called to calculate which editing screen to display based on the mimetype
     * of a document. If appropriate, the in-line editing screen will be shown.
     */
    public void setupEditAction(String path) {
        this.setupContentAction(path, true);

        // retrieve the content reader for this node
        String avmPath = this.getAvmActionNode().getPath();
        if (getAvmService().hasAspect(-1, avmPath, WCMAppModel.ASPECT_RENDITION)) {
            if (logger.isDebugEnabled()) {
                logger.debug(avmPath + " is a rendition, editing primary rendition instead");
            }

            try {
                final FormInstanceData fid = this.getFormsService().getRendition(-1, avmPath)
                        .getPrimaryFormInstanceData();
                avmPath = fid.getPath();

                if (logger.isDebugEnabled()) {
                    logger.debug("Editing primary form instance data " + avmPath);
                }

                this.setAvmActionNode(new AVMNode(getAvmService().lookup(-1, avmPath)));
            } catch (IllegalArgumentException iae) {
                //Utils.addErrorMessage(iae.getMessage(), iae);
                logger.warn(iae);
            } catch (FileNotFoundException fnfe) {
                //Utils.addErrorMessage(fnfe.getMessage(), fnfe);
                logger.warn(fnfe);
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Editing AVM node: " + avmPath);
        }

        String outcome = null;
        // calculate which editor screen to display
        if (getAvmService().hasAspect(-1, avmPath, WCMAppModel.ASPECT_FORM_INSTANCE_DATA)) {
            // make content available to the editing screen
            try {
                // make sure the form association works before proceeding to the
                // edit web content wizard
                this.getFormsService().getFormInstanceData(-1, avmPath).getForm();
                // navigate to appropriate screen
                outcome = "wizard:editWebContent";
            } catch (FormNotFoundException fnfe) {
                //Utils.addErrorMessage(fnfe.getMessage(), fnfe);
                logger.warn(fnfe);

                final Map<String, String> params = new HashMap<String, String>(2, 1.0f);
                params.put("finishOutcome", "wizard:editWebContent");
                params.put("cancelOutcome", "dialog:editAvmFile");
                Application.getDialogManager().setupParameters(params);

                outcome = "dialog:promptForWebForm";
            }
        } else {
            // normal downloadable document
            outcome = "dialog:editAvmFile";

            // we need to mark the file as modified so it gets a lock
            this.getAvmService().forceCopy(avmPath);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("outcome " + outcome + " for path " + path);
        }

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

    /**
     * Action handler for all nodes from user sandbox
     */
    public void setupAllItemsAction(ActionEvent event) {
        setupSandboxAction(event);
        this.allItemsAction = true;
    }

    /**
     * Refresh Sandbox in the virtualisation server
     */
    public void refreshSandbox(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        String store = params.get("store");

        if (store == null) {
            store = getStagingStore();
        }

        // update the specified webapp in the store
        String webappPath = AVMUtil.buildStoreWebappPath(store, getWebapp());
        AVMUtil.updateVServerWebapp(webappPath, true);
    }

    /**
     * Undo changes to a single node
     */
    public void revertNode(ActionEvent event) {
        String avmPath = getPathFromEventArgs(event);
        String sbStoreId = WCMUtil.getSandboxStoreId(avmPath);
        List<String> namesForDisplayMsg = new LinkedList<String>();
        UserTransaction tx = null;
        final FacesContext context = FacesContext.getCurrentInstance();
        try {
            tx = Repository.getUserTransaction(context, false);
            tx.begin();

            AVMNodeDescriptor node = getAvmService().lookup(-1, avmPath, true);
            if (node != null) {
                FormInstanceData fid = null;
                if (getAvmService().hasAspect(-1, avmPath, WCMAppModel.ASPECT_RENDITION)) {
                    fid = this.getFormsService().getRendition(-1, avmPath).getPrimaryFormInstanceData();
                } else if (getAvmService().hasAspect(-1, avmPath, WCMAppModel.ASPECT_FORM_INSTANCE_DATA)) {
                    fid = this.getFormsService().getFormInstanceData(-1, avmPath);
                }
                List<String> paths = new ArrayList<String>();
                if (fid != null) {
                    paths.add(WCMUtil.getStoreRelativePath(fid.getPath()));
                    namesForDisplayMsg.add(fid.getName());
                    for (Rendition r : fid.getRenditions()) {
                        paths.add(WCMUtil.getStoreRelativePath(r.getPath()));
                        namesForDisplayMsg.add(r.getName());
                    }
                } else {
                    paths.add(WCMUtil.getStoreRelativePath(avmPath));
                    namesForDisplayMsg.add(node.getName());
                }

                getSandboxService().revertList(sbStoreId, paths);
            }

            // commit the transaction
            tx.commit();

            // if we get here, all was well - output friendly status message to the user
            this.displayStatusMessage(context,
                    MessageFormat.format(Application.getMessage(context, MSG_REVERT_SUCCESS),
                            StringUtils.join(namesForDisplayMsg.toArray(), ", "), namesForDisplayMsg.size()));
        } catch (Throwable err) {
            err.printStackTrace(System.err);
            Utils.addErrorMessage(MessageFormat.format(Application.getMessage(context, Repository.ERROR_GENERIC),
                    err.getMessage()), err);
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        }
    }

    /**
     * Revert a sandbox to a specific snapshot version ID
     */
    public void revertSnapshot(Map<String, String> params) {
        String sandbox = params.get("sandbox");
        String strVersion = params.get("version");
        if (sandbox != null && strVersion != null && strVersion.length() != 0) {
            try {
                getSandboxService().revertSnapshot(sandbox, Integer.valueOf(strVersion));
                FacesContext context = FacesContext.getCurrentInstance();

                // if we get here, all was well - output friendly status message to the user
                this.displayStatusMessage(context, MessageFormat
                        .format(Application.getMessage(context, MSG_REVERT_SANDBOX), sandbox, strVersion));
            } catch (Throwable err) {
                err.printStackTrace(System.err);
                Utils.addErrorMessage(MessageFormat.format(
                        Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC),
                        err.getMessage()), err);
            }
        }
    }

    /**
     * Create web content from a specific Form via the User Sandbox 'Available Forms' panel
     */
    public void createFormContent(ActionEvent event) {
        // setup the correct sandbox for the create action
        this.setupSandboxAction(event);

        // pass form ID to the wizard - to be picked up in init()
        Application.getWizardManager().setupParameters(event);
        final FacesContext fc = FacesContext.getCurrentInstance();
        fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "wizard:createWebContent");
    }

    /**
     * Perform a canned search for previously generated Form content 
     */
    public void searchFormContent(ActionEvent event) {
        // setup the correct sandbox for the canned search
        this.setupSandboxAction(event);

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

        StringBuilder query = new StringBuilder(256);
        query.append("+ASPECT:\"").append(WCMAppModel.ASPECT_FORM_INSTANCE_DATA).append("\"");
        query.append(" -ASPECT:\"").append(WCMAppModel.ASPECT_RENDITION).append("\"");
        query.append(" +@").append(Repository.escapeQName(WCMAppModel.PROP_PARENT_FORM_NAME)).append(":\"")
                .append(formName).append("\"");
        FormSearchContext searchContext = new FormSearchContext();
        searchContext.setCannedQuery(query.toString(), formName);

        // set the search context - when the view is refreshed, this will be detected and
        // the search results mode of the AVM Browse screen will be displayed 
        this.searchContext = searchContext;

        // set the search origin so that when the search is closed we know
        // to go back to the website view and not the browse view (WCM-1007)
        this.searchOrigin = "formContent";
    }

    /**
     * Event handler that transitions a 'submitpending' task to effectively
     * bypass the lauch date and immediately submit the items.
     * 
     * @param event The event
     */
    public void promotePendingSubmission(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        String taskId = params.get("taskId");

        UserTransaction tx = null;
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            tx = Repository.getUserTransaction(context, false);
            tx.begin();

            // transition the task
            this.getWorkflowService().endTask(taskId, "launch");

            // commit the transaction
            tx.commit();
        } catch (Throwable err) {
            Utils.addErrorMessage(MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC),
                    err.getMessage()), err);
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        }
    }

    /**
     * Event handler that cancels a pending submission.
     * 
     * @param event The event
     */
    public void cancelPendingSubmission(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        String workflowId = params.get("workflowInstanceId");

        UserTransaction tx = null;
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            tx = Repository.getUserTransaction(context, false);
            tx.begin();

            // cancel the workflow
            this.getWorkflowService().cancelWorkflow(workflowId);

            // commit the transaction
            tx.commit();
        } catch (Throwable err) {
            Utils.addErrorMessage(MessageFormat.format(
                    Application.getMessage(FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC),
                    err.getMessage()), err);
            try {
                if (tx != null) {
                    tx.rollback();
                }
            } catch (Exception tex) {
            }
        }
    }

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

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

    /**
     * Perform a lucene search of the website
     */
    public void searchWebsite(ActionEvent event) {
        if (this.websiteQuery != null && this.websiteQuery.length() != 0) {
            SearchContext searchContext = new SearchContext();

            searchContext.setText(this.websiteQuery);

            // set the search context - when the view is refreshed, this will be detected and
            // the search results mode of the AVM Browse screen will be displayed 
            this.searchContext = searchContext;

            resetFileFolderLists();

            if (searchContext.buildQuery(getMinimumSearchLength()) == 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(), BrowseBean.MSG_SEARCH_MINIMUM),
                        new Object[] { getMinimumSearchLength() }));
            }
        }
    }

    /**
     * Action called to Close the search dialog by returning to the last viewed path
     */
    public void closeSearch(ActionEvent event) {
        this.searchContext = null;
        resetFileFolderLists();

        if (this.searchOrigin != null) {
            // if the search was from elsewhere navigate back to the website view
            this.searchOrigin = null;

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

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

    /**
     * Display a status message to the user
     * 
     * @param context
     * @param msg        Text message to display
     */
    /*package*/ void displayStatusMessage(FacesContext context, String msg) {
        FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg);
        context.addMessage(FORM_ID + ':' + COMPONENT_SANDBOXESPANEL, facesMsg);
    }

    /*package*/ boolean isCurrentPathNull() {
        return (this.currentPath == null);
    }

    /**
     * Initialise default values from client configuration
     */
    private void initFromClientConfig() {
        ConfigService config = Application.getConfigService(FacesContext.getCurrentInstance());
        ConfigElement wcmConfig = config.getGlobalConfig().getConfigElement("wcm");
        if (wcmConfig != null) {
            ConfigElement viewsConfig = wcmConfig.getChild("views");
            if (viewsConfig != null) {
                ConfigElement pageConfig = viewsConfig.getChild("browse-page-size");
                if (pageConfig != null) {
                    String strPageSize = pageConfig.getValue();
                    if (strPageSize != null) {
                        int pageSize = Integer.valueOf(strPageSize.trim());
                        setPageSizeFiles(pageSize);
                        setPageSizeFolders(pageSize);
                    }
                }
            }
        }
    }

    /**
     * Update the breadcrumb with the clicked Folder path location
     */
    private void updateUILocation(String path) {
        // fully update the entire breadcrumb path - i.e. do not append as may
        // have navigated from deeper sub-folder (search results)
        int sandboxPathLength = AVMUtil.getSandboxPath(path).length();
        String currentPath = path;
        this.location.clear();
        while (currentPath.length() != sandboxPathLength) {
            this.location.add(new AVMBreadcrumbHandler(currentPath));
            currentPath = AVMNodeConverter.SplitBase(currentPath)[0];
        }
        Collections.reverse(this.location);

        setCurrentPath(path);
    }

    /**
     * @return the path from the 'id' argument in the specified UIActionLink event
     */
    private String getPathFromEventArgs(ActionEvent event) {
        UIActionLink link = (UIActionLink) event.getComponent();
        Map<String, String> params = link.getParameterMap();
        return params.get("id");
    }

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

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

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

        // clear webapp listing as we may have returned from the Create Webapp dialog
        this.webapps = null;
    }

    private void resetFileFolderLists() {
        if (this.foldersRichList != null) {
            this.foldersRichList.setValue(null);
        }
        if (this.filesRichList != null) {
            this.filesRichList.setValue(null);
        }

        this.files = null;
        this.folders = null;

        // reset the WebProject instance - as values may be cached that have now changed
        this.webProject = null;
    }

    /**
     * @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
    }

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

    /**
     * Class to handle breadcrumb interaction for AVM page
     */
    @SuppressWarnings("serial")
    private class AVMBreadcrumbHandler implements IBreadcrumbHandler {
        private String path;

        AVMBreadcrumbHandler(String path) {
            this.path = path;
        }

        @SuppressWarnings("unchecked")
        public String navigationOutcome(UIBreadcrumb breadcrumb) {
            setCurrentPath(path);
            setLocation((List) breadcrumb.getValue());
            return null;
        }

        @Override
        public String toString() {
            if (AVMUtil.buildSandboxRootPath(getSandbox()).equals(path)) {
                // don't display the 'root' webapps path as this will confuse users
                // instead display which sandbox we are in
                String label = username;
                if (label == null) {
                    label = Application.getMessage(FacesContext.getCurrentInstance(), MSG_SANDBOXSTAGING);
                }
                return label;
            } else {
                return path.substring(path.lastIndexOf('/') + 1);
            }
        }
    }

    /**
     * Wrap SearchContext to allow prebuilt canned Form query to be apply as search context
     */
    public class FormSearchContext extends SearchContext {
        private String cannedQuery = null;
        private String formName;

        public void setCannedQuery(String q, String formName) {
            this.cannedQuery = q;
            this.formName = formName;
        }

        @Override
        public String buildQuery(int minimum) {
            return (this.cannedQuery == null ? super.buildQuery(minimum) : this.cannedQuery);
        }

        @Override
        public String getText() {
            return (this.cannedQuery == null ? super.getText()
                    : MessageFormat.format(
                            Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_FORM_CONTENT),
                            this.formName));
        }
    }

    /**
     * Revert All Conflicts
     * 
     * @param event
     */
    public void revertAllConflict(ActionEvent event) {
        final HtmlCommandButton button = (HtmlCommandButton) event.getComponent();

        List<Object> params = button.getChildren();
        String userStorePath = null;
        //String stagingStorePath = null;
        for (Object obj : params) {
            UIParameter uip = (UIParameter) obj;
            if (uip.getName().equals("userStorePath")) {
                userStorePath = (String) uip.getValue();
            }
            /*
            if (uip.getName().equals("stagingStorePath"))
            {
               stagingStore = (String) uip.getValue();
            }
            */
        }
        String[] storePath = WCMUtil.splitPath(userStorePath);

        List<AssetInfo> assets = sbService.listChanged(storePath[0], storePath[1], true);

        String sbStoreId = storePath[0];

        List<String> paths = new ArrayList<String>();
        for (AssetInfo asset : assets) {
            if (asset.getDiffCode() == AVMDifference.CONFLICT) {
                if (!AVMWorkflowUtil.isInActiveWorkflow(sbStoreId, asset.getPath())) {
                    paths.add(asset.getPath());
                }
            }
        }

        sbService.revertList(sbStoreId, paths);
    }
}