From d6bebaadfc462ba501cd4682c9dd3870d73c4bdd Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Fri, 7 Mar 2008 02:55:43 +0000 Subject: [PATCH] Merged V2.2 to HEAD 7740: Fix to display better error messages for errors when restricting WCM File Picker to configured search 7993: Added missing explicit AVM index. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@8449 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/messages/webclient.properties | 2 + .../alfresco/web/bean/wcm/FilePickerBean.java | 2124 +++++++++-------- 2 files changed, 1072 insertions(+), 1054 deletions(-) diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 66a12ad5ed..1be9f6b5c1 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -1833,6 +1833,8 @@ error_association=Failed to find association definition for association \"{0}\". error_charset_null=Null characterset value error_negative_quota=Quota cannot be negative: {0} error_search_not_exist=Search does not exist with name: {0} +error_search_not_exist=Search does not exist with name: \"{0}\" +error_retrieving_search_results=Error getting results for search \"{0}\" - \"{1}\" # Confirmations return_to_application=Return to application diff --git a/source/java/org/alfresco/web/bean/wcm/FilePickerBean.java b/source/java/org/alfresco/web/bean/wcm/FilePickerBean.java index b9ebfb111f..85d43d8527 100644 --- a/source/java/org/alfresco/web/bean/wcm/FilePickerBean.java +++ b/source/java/org/alfresco/web/bean/wcm/FilePickerBean.java @@ -1,1054 +1,1070 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.web.bean.wcm; - -import java.io.InputStream; -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.faces.context.ExternalContext; -import javax.faces.context.FacesContext; -import javax.faces.context.ResponseWriter; -import javax.servlet.http.HttpServletRequest; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMModel; -import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -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.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.ISO9075; -import org.alfresco.util.Pair; -import org.alfresco.web.app.Application; -import org.alfresco.web.app.servlet.ajax.InvokeCommand; -import org.alfresco.web.bean.FileUploadBean; -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.forms.XMLUtil; -import org.alfresco.web.ui.common.Utils; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.dom4j.XPath; -import org.dom4j.io.SAXReader; -import org.springframework.util.FileCopyUtils; - -/** - * Bean for interacting with the file picker widget using ajax requests. - */ -public class FilePickerBean implements Serializable -{ - private static final Log LOGGER = LogFactory.getLog(FilePickerBean.class); - - private static final String CONFIGURED_SEARCH_QUERY_XPATH = "/search/query"; - private static final String CDATA_START_DELIM = "![CDATA["; - private static final String CDATA_END_DELIM = "]]"; - - // parameter names - private static final String PARAM_FOLDER_RESTRICTION = "folderRestriction"; - private static final String PARAM_CONFIGURED_SEARCH_NAME = "configSearchName"; - private static final String PARAM_SELECTABLE_TYPES = "selectableTypes"; - private static final String PARAM_FILTER_MIME_TYPES = "filterMimetypes"; - private static final String PARAM_CURRENT_PATH = "currentPath"; - - private final Set uploads = new HashSet(); - - // property instance variables - private AVMBrowseBean avmBrowseBean; - transient private AVMService avmService; - transient private NamespaceService namespaceService; - transient private SearchService searchService; - transient private NodeService nodeService; - transient private DictionaryService dictionaryService; - transient private ContentService contentService; - - // cached reference to the public saved searches folder - private NodeRef publicSearchesRef = null; - - // initial current folder - the current folder path which the - // the file picker opens at when first selected in the form - private String initialCurrentPath = null; - - public FilePickerBean() - { - } - - public void clearUploadedFiles() - { - this.uploads.clear(); - } - - public NodeRef[] getUploadedFiles() - { - return (NodeRef[]) this.uploads.toArray(new NodeRef[this.uploads.size()]); - } - - /** - * Set avmBrowseBean property for this bean - * - * @param avmBrowseBean - * the AVMBrowseBean object to pass into this property - */ - public void setAvmBrowseBean(final AVMBrowseBean avmBrowseBean) - { - this.avmBrowseBean = avmBrowseBean; - } - - /** - * Get avmBrowseBean property for this bean - * - * @return avmBrowseBean property value for this bean - */ - public AVMBrowseBean getAvmBrowseBean() - { - return this.avmBrowseBean; - } - - /** - * Set avmService property for this bean - * - * @param avmService - * the avmService object to pass into this property - */ - public void setAvmService(final AVMService avmService) - { - this.avmService = avmService; - } - - /** - * Get avmService property for this bean - * - * @return avmService property value for this bean - */ - public AVMService getAvmService() - { - if (this.avmService == null) - { - this.avmService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getAVMService(); - } - return this.avmService; - } - - /** - * Set nodeService property value for this bean - * - * @param nodeService - * the NodeService object to pass into this property - */ - public void setNodeService(final NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Get nodeService property for this bean - * - * @return nodeService property value for this bean - */ - public NodeService getNodeService() - { - if (this.nodeService == null) - { - this.nodeService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getNodeService(); - } - return this.nodeService; - } - - /** - * Set searchService property for this bean - * - * @param searchService - * the SearchService object to pass into this property - */ - public void setSearchService(final SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Get SearchService property for this bean - * - * @return searchService property value for this bean - */ - public SearchService getSearchService() - { - if (this.searchService == null) - { - this.searchService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getSearchService(); - } - return this.searchService; - } - - /** - * Set dictionaryService property value for this bean - * - * @param dictionaryService - * the DictionaryService object to pass into this property - */ - public void setDictionaryService(final DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Get dictionaryService property for this bean - * - * @return dictionaryService property value for this bean - */ - public DictionaryService getDictionaryService() - { - if (this.dictionaryService == null) - { - this.dictionaryService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getDictionaryService(); - } - return this.dictionaryService; - } - - /** - * Set namespaceService property for this bean - * - * @param namespaceService - * the NamespaceService object to pass into this property - */ - public void setNamespaceService(final NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Get the namespaceService property value for this bean - * - * @return namespaceService property value for this bean - */ - public NamespaceService getNamespaceService() - { - if (this.namespaceService == null) - { - this.namespaceService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getNamespaceService(); - } - return this.namespaceService; - } - - /** - * Set contentService property - * - * @param contentService - * the ContentService object to pass into this property - */ - public void setContentService(final ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Get contentService property value for this bean - * - * @return contentService property value for this bean - */ - public ContentService getContentService() - { - if (this.contentService == null) - { - this.contentService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getContentService(); - } - return this.contentService; - } - - /** - * Provides data for a file picker widget. - */ - @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_XML) - public void getFilePickerData() throws Exception - { - final FacesContext facesContext = FacesContext.getCurrentInstance(); - final ExternalContext externalContext = facesContext.getExternalContext(); - - // get configured search name parameter value - String configSearchName = null; - String[] configSearchNameParam = (String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_CONFIGURED_SEARCH_NAME); - if ((configSearchNameParam != null) - && (configSearchNameParam.length != 0)) - { - configSearchName = configSearchNameParam[0]; - } - - // get selectableTypes parameter value - final QName[] selectableTypes = this - .getSelectableTypes((String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_SELECTABLE_TYPES)); - - // / get filterMimetypes parameter value - final Pattern[] filterMimetypes = this - .getFilterMimetypes((String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_FILTER_MIME_TYPES)); - - // get 'folderRestriction' parameter value - // expecting a relative AVM folder path to be held in this parameter - // (relative to web project webapp root) - String folderPathRestriction = null; - String[] folderPathRestrictionParam = (String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_FOLDER_RESTRICTION); - if ((folderPathRestrictionParam != null) - && (folderPathRestrictionParam.length != 0)) - { - folderPathRestriction = folderPathRestrictionParam[0]; - - // remove leading '/' or '\' (if present) from path restriction - if ((folderPathRestriction.charAt(0) == '/') - || (folderPathRestriction.charAt(0) == '\\')) - { - folderPathRestriction = folderPathRestriction.substring(1); - } - } - - // ### - // the following section sets file picker current path (the folder that - // file picker - // is opened at when selected/changed in the form) - - String currentPath = null; - - // get current path request parameter - String currentPathReqParam = (String) externalContext - .getRequestParameterMap().get(PARAM_CURRENT_PATH); - - // if current path request parameter null then set current path to the - // current AVM path - if ((currentPathReqParam == null)) - { - currentPath = this.getCurrentAVMPath(); - } - // else set current path to current path request parameter converted to - // AVM preview - // store path - else - { - final String previewStorePath = AVMUtil - .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); - currentPath = AVMUtil.buildPath(previewStorePath, currentPathReqParam, - AVMUtil.PathRelation.WEBAPP_RELATIVE); - } - - // if initial current path not set then set it to the current path - if (initialCurrentPath == null) - { - initialCurrentPath = currentPath; - - // insert '/' at end of initial current path if there isn't one - if (initialCurrentPath.charAt(initialCurrentPath.length() - 1) != '/') - { - initialCurrentPath = initialCurrentPath + "/"; - } - } - - // if folder path restriction (relative path) is set, - // then calculate the absolute restriction path (in context of file - // picker's - // initial current path - the path at which it was opened the first time) - // and set file picker current path to that - if ((folderPathRestriction != null) - && (folderPathRestriction.length() != 0)) - { - currentPath = initialCurrentPath + folderPathRestriction; - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug(this + ".getFilePickerData(path = " + currentPath - + ", selectableTypes = [" - + StringUtils.join(selectableTypes, ",") - + "], filterMimetypes = [" - + StringUtils.join(filterMimetypes, ",") + "])"); - } - - // create file picker data XML document to return - // and append file picker data element to it - final org.w3c.dom.Document filePickerDataDoc = XMLUtil.newDocument(); - final org.w3c.dom.Element filePickerDataElement = filePickerDataDoc - .createElement("file-picker-data"); - filePickerDataDoc.appendChild(filePickerDataElement); - - // make sure that there is a node associated with current path - // if not, set an applicable error message as an attribute on - // the file picker data element - final AVMNodeDescriptor currentNode = this.getAvmService().lookup(-1, - currentPath); - - // if the current node is null (path held in current path variable is - // invalid), then add an error attribute to the file picker data - if (currentNode == null) - { - currentPath = AVMUtil.getWebappRelativePath(currentPath); - - filePickerDataElement.setAttribute("error", MessageFormat.format( - Application.getMessage(facesContext, "error_not_found"), - currentPath.substring(currentPath.lastIndexOf("/") + 1, - currentPath.length()), - (currentPath.lastIndexOf("/") == 0 ? "/" : currentPath - .substring(0, currentPath.lastIndexOf("/"))))); - - // If folder restriction has been set, since the derived - // current path is invalid just set it to null - if ((folderPathRestriction != null) - && (folderPathRestriction.length() != 0)) - { - currentPath = null; - } else - // otherwise folder restriction has not been set, so it should be safe - // to fall back to setting the current path to the current AVM path - // (as was the behaviour before the folder restriction feature - // was added) - { - currentPath = this.getCurrentAVMPath(); - } - } - // else current node is not null (path held in current path variable is - // valid) - else - { - // if node for current path points to a file instead of a directory, - // then make sure that the current path points to - // just the directory part of the path - if (!currentNode.isDirectory()) - { - currentPath = AVMNodeConverter.SplitBase(currentPath)[0]; - } - } - - // create current-node element representing node for current path - // and append it to the file picker data element - org.w3c.dom.Element currentNodeElement = filePickerDataDoc - .createElement("current-node"); - if (currentPath == null) - { - currentNodeElement.setAttribute("avmPath", ""); - currentNodeElement.setAttribute("webappRelativePath", ""); - } else - { - currentNodeElement.setAttribute("avmPath", currentPath); - currentNodeElement.setAttribute("webappRelativePath", AVMUtil - .getWebappRelativePath(currentPath)); - } - currentNodeElement.setAttribute("type", "directory"); - currentNodeElement.setAttribute("image", "/images/icons/space_small.gif"); - - filePickerDataElement.appendChild(currentNodeElement); - - // if configured search name supplied (i.e. neither null nor empty), - // then get configured search node matching given name - // and add the nodes from the search result to the - // file-picker-data element - if ((configSearchName != null) && (configSearchName.length() != 0)) - { - // get node reference for named configured search - NodeRef configuredSearchNodeRef = getConfiguredSearches(configSearchName); - - // if configured search node ref is null, then there is no - // configured search matching the name in the 'config search name' - // parameter, so add error message as attribute to file-picker-data - // element - if (configuredSearchNodeRef == null) - { - filePickerDataElement.setAttribute("error", MessageFormat.format( - Application.getMessage(facesContext, "error_search_not_exist"), - configSearchName)); - } - else - // else node ref for named configured search is not null, then - // add content nodes from search results as child elements of - // the file picker data element. - { - addSearchResultNodes(filePickerDataDoc, filePickerDataElement, - configuredSearchNodeRef, selectableTypes, facesContext); - } - } - else - { - // add elements for child nodes of current path to file picker - // data element if current path is not null - if (currentPath != null) - { - addPathChildNodesToElement(filePickerDataDoc, - filePickerDataElement, currentPath, selectableTypes, - filterMimetypes, facesContext); - } - } - - final ResponseWriter out = facesContext.getResponseWriter(); - XMLUtil.print(filePickerDataDoc, out); - } - - @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_HTML) - public void uploadFile() throws Exception - { - LOGGER.debug(this + ".uploadFile()"); - final FacesContext facesContext = FacesContext.getCurrentInstance(); - final ExternalContext externalContext = facesContext.getExternalContext(); - final HttpServletRequest request = (HttpServletRequest) externalContext - .getRequest(); - - final ServletFileUpload upload = new ServletFileUpload( - new DiskFileItemFactory()); - upload.setHeaderEncoding("UTF-8"); - final List fileItems = upload.parseRequest(request); - final FileUploadBean bean = new FileUploadBean(); - String uploadId = null; - String currentPath = null; - String filename = null; - String returnPage = null; - InputStream fileInputStream = null; - for (FileItem item : fileItems) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("item = " + item); - } - if (item.isFormField() && item.getFieldName().equals("upload-id")) - { - uploadId = item.getString(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("uploadId is " + uploadId); - } - } - if (item.isFormField() && item.getFieldName().equals("return-page")) - { - returnPage = item.getString(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("returnPage is " + returnPage); - } - } else if (item.isFormField() - && item.getFieldName().equals("currentPath")) - { - final String previewStorePath = AVMUtil - .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); - currentPath = AVMUtil.buildPath(previewStorePath, item.getString(), - AVMUtil.PathRelation.WEBAPP_RELATIVE); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("currentPath is " + currentPath); - } - } else - { - filename = FilenameUtils.getName(item.getName()); - fileInputStream = item.getInputStream(); - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("uploading file " + filename); - } - } - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("saving file " + filename + " to " + currentPath); - } - - try - { - FileCopyUtils.copy(fileInputStream, this.getAvmService().createFile( - currentPath, filename)); - final Map props = new HashMap( - 1, 1.0f); - props.put(ContentModel.PROP_TITLE, new PropertyValue( - DataTypeDefinition.TEXT, filename)); - // props.put(ContentModel.PROP_DESCRIPTION, - // new PropertyValue(DataTypeDefinition.TEXT, - // "Uploaded for form " + this.xformsSession.getForm().getName())); - this.getAvmService().setNodeProperties(currentPath + "/" + filename, - props); - this.getAvmService().addAspect(currentPath + "/" + filename, - ContentModel.ASPECT_TITLED); - - this.uploads.add(AVMNodeConverter.ToNodeRef(-1, currentPath + "/" - + filename)); - returnPage = returnPage.replace("${_FILE_TYPE_IMAGE}", Utils - .getFileTypeImage(facesContext, filename, true)); - } catch (Exception e) - { - LOGGER.debug(e.getMessage(), e); - returnPage = returnPage.replace("${_UPLOAD_ERROR}", e.getMessage()); - } - - LOGGER.debug("upload complete. sending response: " + returnPage); - final org.w3c.dom.Document result = XMLUtil.newDocument(); - final org.w3c.dom.Element htmlEl = result.createElement("html"); - result.appendChild(htmlEl); - final org.w3c.dom.Element bodyEl = result.createElement("body"); - htmlEl.appendChild(bodyEl); - - final org.w3c.dom.Element scriptEl = result.createElement("script"); - bodyEl.appendChild(scriptEl); - scriptEl.setAttribute("type", "text/javascript"); - final org.w3c.dom.Node scriptText = result.createTextNode(returnPage); - scriptEl.appendChild(scriptText); - - final ResponseWriter out = facesContext.getResponseWriter(); - XMLUtil.print(result, out); - } - - private String getCurrentAVMPath() - { - final AVMNode node = this.getAvmBrowseBean().getAvmActionNode(); - if (node == null) - { - return this.getAvmBrowseBean().getCurrentPath(); - } - - final String result = node.getPath(); - return node.isDirectory() ? result - : AVMNodeConverter.SplitBase(result)[0]; - } - - private QName[] getSelectableTypes(final String[] selectableTypes) - { - final QName[] result = (selectableTypes == null ? new QName[] - { WCMModel.TYPE_AVM_CONTENT, WCMModel.TYPE_AVM_FOLDER } - : new QName[selectableTypes.length]); - - if (selectableTypes != null) - { - for (int i = 0; i < selectableTypes.length; i++) - { - result[i] = QName.resolveToQName(this.getNamespaceService(), - selectableTypes[i]); - } - } - return result; - } - - private Pattern[] getFilterMimetypes(final String[] filterMimetypes) - { - final Pattern[] result = filterMimetypes == null ? new Pattern[0] - : new Pattern[filterMimetypes.length]; - if (filterMimetypes != null) - { - for (int i = 0; i < filterMimetypes.length; i++) - { - result[i] = Pattern.compile(filterMimetypes[i].replaceAll("\\*", - "\\.*").replaceAll("\\/", "\\\\/")); - } - } - return result; - } - - /** - * Add child nodes of supplied path to given parent element. Directory - * listing is done on given path. Elements representing the child nodes - * returned in the directory listing are added to the supplied parent - * element. - * - * @param doc - * XML document to which the parent node belongs - * @param parent - * parent element to add given nodes to as child elements - * @param path - * path from which to extract child nodes - * @param selectableTypes - * array of types which are the only ones that should be selectable - * in the file picker - * @param filterMimetypes - * array of MIME type patterns used to filter out child nodes - * extracted from the given path which don't match the given MIME - * type patterns - * @param facesContext - * faces context used to set image attribute on each child element - */ - private void addPathChildNodesToElement(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, String path, QName[] selectableTypes, - Pattern[] filterMimetypes, FacesContext facesContext) - { - // append elements for the child AVM nodes of the current path - // to parent element - for (final Map.Entry entry : this - .getAvmService().getDirectoryListing(-1, path).entrySet()) - { - // if AVM node is a content node and the filter MIME types parameter - // has been set, then only add child element for AVM node if it matches - // one of the specified MIME types in the parameter - if (!entry.getValue().isDirectory() && filterMimetypes.length != 0) - { - final String contentMimetype = this.getAvmService() - .getContentDataForRead(entry.getValue()).getMimetype(); - - boolean matched = false; - for (final Pattern p : filterMimetypes) - { - matched = p.matcher(contentMimetype).matches(); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug(p + ".matches(" + contentMimetype + ") = " - + matched); - } - if (matched) - { - break; - } - } - - // if AVM node MIME type doesn't match any of the types in the - // filter MIME types parameter, then don't do any further processing - // on it and jump back to the start of the AVM node traversal loop - if (!matched) - { - continue; - } - } - - // create child element representing AVM node and add to file picker - // data element - addAVMChildNodeToParentElement(doc, parent, entry.getValue(), - selectableTypes, facesContext); - } - } - - /** - * Add result content nodes from configured search as - * child node elements to the provided parent element - * - * @param doc - * XML document to which the supplied parent node belongs - * @param parent - * parent element to which to add search result content nodes - * as child elements - * @param configuredSearchNodeRef - * configured search node reference for which result content nodes - * are added as child elements to the provided parent element - * @param selectableTypes - * node types which must be marked as selectable in the file - * picker - * @param facesContext - * faces context used to set image attribute on each child element - */ - private void addSearchResultNodes(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, NodeRef configuredSearchNodeRef, - QName[] selectableTypes, FacesContext facesContext) - { - // run configured search to get content nodes returned in search result - List searchResultNodes = runConfiguredSearch(configuredSearchNodeRef); - - for (AVMNodeDescriptor node : searchResultNodes) - { - // create child element representing AVM node and add to file picker - // data element - addAVMChildNodeToParentElement(doc, parent, node, selectableTypes, - facesContext); - } - } - - /** - * Run a configured search represented by the given node reference, against - * the web project the XForm is currently within - * - * @param configuredQueryNodRef - * NodeRef of the configured query with which to run the search - * - * @return content nodes returned by the search - */ - private List runConfiguredSearch( - NodeRef configSearchNodeRef) - { - // get the store id used to run the configured search query - WebProject webProject = this.getAvmBrowseBean().getWebProject(); - String storeID = webProject.getStoreId(); - - // extract the content of configured search node to XML document - ContentReader contentReader = getContentService().getReader( - configSearchNodeRef, ContentModel.PROP_CONTENT); - InputStream queryInpStream = contentReader.getContentInputStream(); - SAXReader reader = new SAXReader(); - Document queryDoc = null; - try - { - queryDoc = reader.read(queryInpStream); - } catch (DocumentException de) - { - // ignore exception and return null - return null; - } - - // extract search query from configured search XML document - String query = null; - XPath queryXPath = DocumentHelper - .createXPath(CONFIGURED_SEARCH_QUERY_XPATH); - List xpathResult = queryXPath.selectNodes(queryDoc); - if ((xpathResult != null) && (xpathResult.size() != 0)) - { - // get the text from the query element - Element queryElement = (Element) xpathResult.get(0); - String queryElemText = queryElement.getText(); - - // now extract the actual search query string from the CDATA section - // within that text - - int cdataStartDelimIndex = queryElemText.indexOf(CDATA_START_DELIM); - int cdataEndDelimIndex = queryElemText.indexOf(CDATA_END_DELIM); - - // if the CDATA start delimiter is found in the query element text - // && there is text between the CDATA start and end delimiters then - // extract - // the query string from the CDATA section - if ((cdataStartDelimIndex > -1) - && ((cdataStartDelimIndex + CDATA_START_DELIM.length()) < cdataEndDelimIndex)) - { - query = queryElemText.substring(cdataStartDelimIndex - + CDATA_START_DELIM.length(), cdataEndDelimIndex); - } - } - - // perform the search against the repository - // if query was extracted from the configured search successfully - // (extracted query non-null) - List resultNodeDescriptors = null; - if ((query != null) && (query.length() != 0)) - { - ResultSet results = null; - try - { - results = this.getSearchService().query( - new StoreRef(StoreRef.PROTOCOL_AVM, storeID), - SearchService.LANGUAGE_LUCENE, query); - - if (results.length() != 0) - { - resultNodeDescriptors = new ArrayList(); - for (int i = 0; i < results.length(); i++) - { - ResultSetRow row = results.getRow(i); - NodeRef resultNodeRef = row.getNodeRef(); - Node resultNode = new Node(resultNodeRef); - - // only add content type nodes to the search result - // as we don't want the user to navigate down into folders - // in the search results - if (getDictionaryService().isSubClass(resultNode.getType(), - ContentModel.TYPE_CONTENT)) - { - Pair pair = AVMNodeConverter - .ToAVMVersionPath(resultNodeRef); - Integer version = pair.getFirst(); - String path = pair.getSecond(); - resultNodeDescriptors.add(getAvmService().lookup(version, - path)); - } - } - } - } catch (Throwable err) - { - throw new AlfrescoRuntimeException("Failed to execute search: " - + query, err); - } finally - { - if (results != null) - { - results.close(); - } - } - } - - return resultNodeDescriptors; - } - - /** - * Get the cached reference to the public saved searches folder. This method - * will first get a reference to the public saved searches folder and assign - * it to the cached reference if is it is null. - * - * @return the cached reference to the public Saved Searches folder - */ - private NodeRef getPublicSearchesRef() - { - // if the cached reference is null, then get a reference to the - // public saved searches folder to assign to it - if (publicSearchesRef == null) - { - // Use the search service get a reference to the - // public saved searches folder. - FacesContext fc = FacesContext.getCurrentInstance(); - String xpath = Application.getRootPath(fc) + "/" - + Application.getGlossaryFolderName(fc) + "/" - + Application.getSavedSearchesFolderName(fc); - - List results = null; - try - { - results = getSearchService().selectNodes( - getNodeService().getRootNode(Repository.getStoreRef()), - xpath, null, getNamespaceService(), false); - } catch (AccessDeniedException err) - { - // ignore and return null - } - - if (results != null && results.size() != 0) - { - publicSearchesRef = results.get(0); - } - } - - return publicSearchesRef; - } - - /** - * Get node for configured search by name. - * - * @param configSearchName - * name of configured search for which to get node - * @return node reference for configured search - */ - public NodeRef getConfiguredSearches(String configSearchName) - { - NodeRef configSearchNodeRef = null; - - // get the folder reference from the - // public searches location - NodeRef publicSearchesFolderRef = getPublicSearchesRef(); - - // read the content nodes under the folder - List childRefs = getNodeService().getChildAssocs( - publicSearchesFolderRef, ContentModel.ASSOC_CONTAINS, - RegexQNamePattern.MATCH_ALL); - - // return content node with name matching given configured search name - if (childRefs.size() != 0) - { - for (ChildAssociationRef ref : childRefs) - { - NodeRef childNodeRef = ref.getChildRef(); - Node childNode = new Node(childNodeRef); - if (getDictionaryService().isSubClass(childNode.getType(), - ContentModel.TYPE_CONTENT)) - { - String childNodeName = childNode.getName(); - if (childNodeName.equals(configSearchName)) - { - configSearchNodeRef = childNodeRef; - break; - } - } - } - } - - return configSearchNodeRef; - } - - /** - * Create child element representing given AVM node and add to given parent - * element - * - * @param doc - * Document to which given parent element belongs - * @param parent - * parent element to add AVM node to as child element - * @param node - * AVM node to add as child node to given parent - * @param selectableTypes - * AVM node types which must be marked as selectable - * @param facesContent - * Faces context used to get file-type icon for given AVM node - */ - private void addAVMChildNodeToParentElement(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, AVMNodeDescriptor node, - QName[] selectableTypes, FacesContext facesContext) - { - // create child node element to add to file picker data - org.w3c.dom.Element childNodeElement = doc.createElement("child-node"); - childNodeElement.setAttribute("avmPath", node.getPath()); - childNodeElement.setAttribute("webappRelativePath", AVMUtil - .getWebappRelativePath(node.getPath())); - childNodeElement.setAttribute("type", node.isDirectory() ? "directory" - : "file"); - - // Set image attribute on each child - // TODO (Glen): IS this the right image to set? - // originally from Ariel's code - childNodeElement.setAttribute("image", - (node.isDirectory() ? "/images/icons/space_small.gif" : Utils - .getFileTypeImage(facesContext, node.getName(), true))); - - boolean selectable = false; - - // set 'selectable' attribute on child node to mark whether node should - // be selectable in file picker or not - // - // TODO Ariel: faking this for now since i can't figure out how to - // efficiently get the type - // qname from the avmservice - for (final QName typeQName : selectableTypes) - { - selectable = selectable - || (WCMModel.TYPE_AVM_FOLDER.equals(typeQName) && node - .isDirectory()); - selectable = selectable - || (WCMModel.TYPE_AVM_CONTENT.equals(typeQName) && !node - .isDirectory()); - } - childNodeElement.setAttribute("selectable", Boolean.toString(selectable)); - - // append child node element to parent - parent.appendChild(childNodeElement); - } -} +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.bean.wcm; + +import java.io.InputStream; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.servlet.http.HttpServletRequest; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.search.SearcherException; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +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.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.Pair; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.ajax.InvokeCommand; +import org.alfresco.web.bean.FileUploadBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.forms.XMLUtil; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.XPath; +import org.dom4j.io.SAXReader; +import org.springframework.util.FileCopyUtils; + +/** + * Bean for interacting with the file picker widget using ajax requests. + */ +public class FilePickerBean implements Serializable +{ + private static final Log LOGGER = LogFactory.getLog(FilePickerBean.class); + + private static final String CONFIGURED_SEARCH_QUERY_XPATH = "/search/query"; + private static final String CDATA_START_DELIM = "![CDATA["; + private static final String CDATA_END_DELIM = "]]"; + + // parameter names + private static final String PARAM_FOLDER_RESTRICTION = "folderRestriction"; + private static final String PARAM_CONFIGURED_SEARCH_NAME = "configSearchName"; + private static final String PARAM_SELECTABLE_TYPES = "selectableTypes"; + private static final String PARAM_FILTER_MIME_TYPES = "filterMimetypes"; + private static final String PARAM_CURRENT_PATH = "currentPath"; + + private final Set uploads = new HashSet(); + + // property instance variables + private AVMBrowseBean avmBrowseBean; + transient private AVMService avmService; + transient private NamespaceService namespaceService; + transient private SearchService searchService; + transient private NodeService nodeService; + transient private DictionaryService dictionaryService; + transient private ContentService contentService; + + // cached reference to the public saved searches folder + private NodeRef publicSearchesRef = null; + + // initial current folder - the current folder path which the + // the file picker opens at when first selected in the form + private String initialCurrentPath = null; + + public FilePickerBean() + { + } + + public void clearUploadedFiles() + { + this.uploads.clear(); + } + + public NodeRef[] getUploadedFiles() + { + return (NodeRef[]) this.uploads.toArray(new NodeRef[this.uploads.size()]); + } + + /** + * Set avmBrowseBean property for this bean + * + * @param avmBrowseBean + * the AVMBrowseBean object to pass into this property + */ + public void setAvmBrowseBean(final AVMBrowseBean avmBrowseBean) + { + this.avmBrowseBean = avmBrowseBean; + } + + /** + * Get avmBrowseBean property for this bean + * + * @return avmBrowseBean property value for this bean + */ + public AVMBrowseBean getAvmBrowseBean() + { + return this.avmBrowseBean; + } + + /** + * Set avmService property for this bean + * + * @param avmService + * the avmService object to pass into this property + */ + public void setAvmService(final AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Get avmService property for this bean + * + * @return avmService property value for this bean + */ + public AVMService getAvmService() + { + if (this.avmService == null) + { + this.avmService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getAVMService(); + } + return this.avmService; + } + + /** + * Set nodeService property value for this bean + * + * @param nodeService + * the NodeService object to pass into this property + */ + public void setNodeService(final NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Get nodeService property for this bean + * + * @return nodeService property value for this bean + */ + public NodeService getNodeService() + { + if (this.nodeService == null) + { + this.nodeService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getNodeService(); + } + return this.nodeService; + } + + /** + * Set searchService property for this bean + * + * @param searchService + * the SearchService object to pass into this property + */ + public void setSearchService(final SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Get SearchService property for this bean + * + * @return searchService property value for this bean + */ + public SearchService getSearchService() + { + if (this.searchService == null) + { + this.searchService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getSearchService(); + } + return this.searchService; + } + + /** + * Set dictionaryService property value for this bean + * + * @param dictionaryService + * the DictionaryService object to pass into this property + */ + public void setDictionaryService(final DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Get dictionaryService property for this bean + * + * @return dictionaryService property value for this bean + */ + public DictionaryService getDictionaryService() + { + if (this.dictionaryService == null) + { + this.dictionaryService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getDictionaryService(); + } + return this.dictionaryService; + } + + /** + * Set namespaceService property for this bean + * + * @param namespaceService + * the NamespaceService object to pass into this property + */ + public void setNamespaceService(final NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Get the namespaceService property value for this bean + * + * @return namespaceService property value for this bean + */ + public NamespaceService getNamespaceService() + { + if (this.namespaceService == null) + { + this.namespaceService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getNamespaceService(); + } + return this.namespaceService; + } + + /** + * Set contentService property + * + * @param contentService + * the ContentService object to pass into this property + */ + public void setContentService(final ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Get contentService property value for this bean + * + * @return contentService property value for this bean + */ + public ContentService getContentService() + { + if (this.contentService == null) + { + this.contentService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getContentService(); + } + return this.contentService; + } + + /** + * Provides data for a file picker widget. + */ + @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_XML) + public void getFilePickerData() throws Exception + { + final FacesContext facesContext = FacesContext.getCurrentInstance(); + final ExternalContext externalContext = facesContext.getExternalContext(); + + // get configured search name parameter value + String configSearchName = null; + String[] configSearchNameParam = (String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_CONFIGURED_SEARCH_NAME); + if ((configSearchNameParam != null) + && (configSearchNameParam.length != 0)) + { + configSearchName = configSearchNameParam[0]; + } + + // get selectableTypes parameter value + final QName[] selectableTypes = this + .getSelectableTypes((String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_SELECTABLE_TYPES)); + + // / get filterMimetypes parameter value + final Pattern[] filterMimetypes = this + .getFilterMimetypes((String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_FILTER_MIME_TYPES)); + + // get 'folderRestriction' parameter value + // expecting a relative AVM folder path to be held in this parameter + // (relative to web project webapp root) + String folderPathRestriction = null; + String[] folderPathRestrictionParam = (String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_FOLDER_RESTRICTION); + if ((folderPathRestrictionParam != null) + && (folderPathRestrictionParam.length != 0)) + { + folderPathRestriction = folderPathRestrictionParam[0]; + + // remove leading '/' or '\' (if present) from path restriction + if ((folderPathRestriction.charAt(0) == '/') + || (folderPathRestriction.charAt(0) == '\\')) + { + folderPathRestriction = folderPathRestriction.substring(1); + } + } + + // ### + // the following section sets file picker current path (the folder that + // file picker + // is opened at when selected/changed in the form) + + String currentPath = null; + + // get current path request parameter + String currentPathReqParam = (String) externalContext + .getRequestParameterMap().get(PARAM_CURRENT_PATH); + + // if current path request parameter null then set current path to the + // current AVM path + if ((currentPathReqParam == null)) + { + currentPath = this.getCurrentAVMPath(); + } + // else set current path to current path request parameter converted to + // AVM preview + // store path + else + { + final String previewStorePath = AVMUtil + .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); + currentPath = AVMUtil.buildPath(previewStorePath, currentPathReqParam, + AVMUtil.PathRelation.WEBAPP_RELATIVE); + } + + // if initial current path not set then set it to the current path + if (initialCurrentPath == null) + { + initialCurrentPath = currentPath; + + // insert '/' at end of initial current path if there isn't one + if (initialCurrentPath.charAt(initialCurrentPath.length() - 1) != '/') + { + initialCurrentPath = initialCurrentPath + "/"; + } + } + + // if folder path restriction (relative path) is set, + // then calculate the absolute restriction path (in context of file + // picker's + // initial current path - the path at which it was opened the first time) + // and set file picker current path to that + if ((folderPathRestriction != null) + && (folderPathRestriction.length() != 0)) + { + currentPath = initialCurrentPath + folderPathRestriction; + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(this + ".getFilePickerData(path = " + currentPath + + ", selectableTypes = [" + + StringUtils.join(selectableTypes, ",") + + "], filterMimetypes = [" + + StringUtils.join(filterMimetypes, ",") + "])"); + } + + // create file picker data XML document to return + // and append file picker data element to it + final org.w3c.dom.Document filePickerDataDoc = XMLUtil.newDocument(); + final org.w3c.dom.Element filePickerDataElement = filePickerDataDoc + .createElement("file-picker-data"); + filePickerDataDoc.appendChild(filePickerDataElement); + + // make sure that there is a node associated with current path + // if not, set an applicable error message as an attribute on + // the file picker data element + final AVMNodeDescriptor currentNode = this.getAvmService().lookup(-1, + currentPath); + + // if the current node is null (path held in current path variable is + // invalid), then add an error attribute to the file picker data + if (currentNode == null) + { + currentPath = AVMUtil.getWebappRelativePath(currentPath); + + filePickerDataElement.setAttribute("error", MessageFormat.format( + Application.getMessage(facesContext, "error_not_found"), + "'" + currentPath.substring(currentPath.lastIndexOf("/") + 1, currentPath.length()) + "'", + (currentPath.lastIndexOf("/") == 0 ? "/" : currentPath.substring( + 0, currentPath.lastIndexOf("/"))))); + + // If folder restriction has been set, since the derived + // current path is invalid just set it to null + if ((folderPathRestriction != null) + && (folderPathRestriction.length() != 0)) + { + currentPath = null; + } else + // otherwise folder restriction has not been set, so it should be safe + // to fall back to setting the current path to the current AVM path + // (as was the behaviour before the folder restriction feature + // was added) + { + currentPath = this.getCurrentAVMPath(); + } + } + // else current node is not null (path held in current path variable is + // valid) + else + { + // if node for current path points to a file instead of a directory, + // then make sure that the current path points to + // just the directory part of the path + if (!currentNode.isDirectory()) + { + currentPath = AVMNodeConverter.SplitBase(currentPath)[0]; + } + } + + // create current-node element representing node for current path + // and append it to the file picker data element + org.w3c.dom.Element currentNodeElement = filePickerDataDoc + .createElement("current-node"); + if (currentPath == null) + { + currentNodeElement.setAttribute("avmPath", ""); + currentNodeElement.setAttribute("webappRelativePath", ""); + } else + { + currentNodeElement.setAttribute("avmPath", currentPath); + currentNodeElement.setAttribute("webappRelativePath", AVMUtil + .getWebappRelativePath(currentPath)); + } + currentNodeElement.setAttribute("type", "directory"); + currentNodeElement.setAttribute("image", "/images/icons/space_small.gif"); + + filePickerDataElement.appendChild(currentNodeElement); + + // if configured search name supplied (i.e. neither null nor empty), + // then get configured search node matching given name + // and add the nodes from the search result to the + // file-picker-data element + if ((configSearchName != null) && (configSearchName.length() != 0)) + { + // get node reference for named configured search + NodeRef configuredSearchNodeRef = getConfiguredSearches(configSearchName); + + // if configured search node ref is null, then there is no + // configured search matching the name in the 'config search name' + // parameter, so add error message as attribute to file-picker-data + // element + if (configuredSearchNodeRef == null) + { + filePickerDataElement.setAttribute("error", MessageFormat.format( + Application.getMessage(facesContext, "error_search_not_exist"), + configSearchName)); + } + else + // else node ref for named configured search is not null, then + // add content nodes from search results as child elements of + // the file picker data element. + { + try + { + addSearchResultNodes(filePickerDataDoc, filePickerDataElement, + configuredSearchNodeRef, selectableTypes, facesContext); + } + // if searcher exception thrown whilst getting search results, + // then add error message as attribute to file-picker-data element + catch (SearcherException e) + { + filePickerDataElement.setAttribute("error", MessageFormat.format( + Application.getMessage(facesContext, "error_retrieving_search_results"), + configSearchName, e.getMessage())); + } + } + } + else + { + // add elements for child nodes of current path to file picker + // data element if current path is not null + if (currentPath != null) + { + addPathChildNodesToElement(filePickerDataDoc, + filePickerDataElement, currentPath, selectableTypes, + filterMimetypes, facesContext); + } + } + + final ResponseWriter out = facesContext.getResponseWriter(); + XMLUtil.print(filePickerDataDoc, out); + } + + @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_HTML) + public void uploadFile() throws Exception + { + LOGGER.debug(this + ".uploadFile()"); + final FacesContext facesContext = FacesContext.getCurrentInstance(); + final ExternalContext externalContext = facesContext.getExternalContext(); + final HttpServletRequest request = (HttpServletRequest) externalContext + .getRequest(); + + final ServletFileUpload upload = new ServletFileUpload( + new DiskFileItemFactory()); + upload.setHeaderEncoding("UTF-8"); + final List fileItems = upload.parseRequest(request); + final FileUploadBean bean = new FileUploadBean(); + String uploadId = null; + String currentPath = null; + String filename = null; + String returnPage = null; + InputStream fileInputStream = null; + for (FileItem item : fileItems) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("item = " + item); + } + if (item.isFormField() && item.getFieldName().equals("upload-id")) + { + uploadId = item.getString(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("uploadId is " + uploadId); + } + } + if (item.isFormField() && item.getFieldName().equals("return-page")) + { + returnPage = item.getString(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("returnPage is " + returnPage); + } + } else if (item.isFormField() + && item.getFieldName().equals("currentPath")) + { + final String previewStorePath = AVMUtil + .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); + currentPath = AVMUtil.buildPath(previewStorePath, item.getString(), + AVMUtil.PathRelation.WEBAPP_RELATIVE); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("currentPath is " + currentPath); + } + } else + { + filename = FilenameUtils.getName(item.getName()); + fileInputStream = item.getInputStream(); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("uploading file " + filename); + } + } + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("saving file " + filename + " to " + currentPath); + } + + try + { + FileCopyUtils.copy(fileInputStream, this.getAvmService().createFile( + currentPath, filename)); + final Map props = new HashMap( + 1, 1.0f); + props.put(ContentModel.PROP_TITLE, new PropertyValue( + DataTypeDefinition.TEXT, filename)); + // props.put(ContentModel.PROP_DESCRIPTION, + // new PropertyValue(DataTypeDefinition.TEXT, + // "Uploaded for form " + this.xformsSession.getForm().getName())); + this.getAvmService().setNodeProperties(currentPath + "/" + filename, + props); + this.getAvmService().addAspect(currentPath + "/" + filename, + ContentModel.ASPECT_TITLED); + + this.uploads.add(AVMNodeConverter.ToNodeRef(-1, currentPath + "/" + + filename)); + returnPage = returnPage.replace("${_FILE_TYPE_IMAGE}", Utils + .getFileTypeImage(facesContext, filename, true)); + } catch (Exception e) + { + LOGGER.debug(e.getMessage(), e); + returnPage = returnPage.replace("${_UPLOAD_ERROR}", e.getMessage()); + } + + LOGGER.debug("upload complete. sending response: " + returnPage); + final org.w3c.dom.Document result = XMLUtil.newDocument(); + final org.w3c.dom.Element htmlEl = result.createElement("html"); + result.appendChild(htmlEl); + final org.w3c.dom.Element bodyEl = result.createElement("body"); + htmlEl.appendChild(bodyEl); + + final org.w3c.dom.Element scriptEl = result.createElement("script"); + bodyEl.appendChild(scriptEl); + scriptEl.setAttribute("type", "text/javascript"); + final org.w3c.dom.Node scriptText = result.createTextNode(returnPage); + scriptEl.appendChild(scriptText); + + final ResponseWriter out = facesContext.getResponseWriter(); + XMLUtil.print(result, out); + } + + private String getCurrentAVMPath() + { + final AVMNode node = this.getAvmBrowseBean().getAvmActionNode(); + if (node == null) + { + return this.getAvmBrowseBean().getCurrentPath(); + } + + final String result = node.getPath(); + return node.isDirectory() ? result + : AVMNodeConverter.SplitBase(result)[0]; + } + + private QName[] getSelectableTypes(final String[] selectableTypes) + { + final QName[] result = (selectableTypes == null ? new QName[] + { WCMModel.TYPE_AVM_CONTENT, WCMModel.TYPE_AVM_FOLDER } + : new QName[selectableTypes.length]); + + if (selectableTypes != null) + { + for (int i = 0; i < selectableTypes.length; i++) + { + result[i] = QName.resolveToQName(this.getNamespaceService(), + selectableTypes[i]); + } + } + return result; + } + + private Pattern[] getFilterMimetypes(final String[] filterMimetypes) + { + final Pattern[] result = filterMimetypes == null ? new Pattern[0] + : new Pattern[filterMimetypes.length]; + if (filterMimetypes != null) + { + for (int i = 0; i < filterMimetypes.length; i++) + { + result[i] = Pattern.compile(filterMimetypes[i].replaceAll("\\*", + "\\.*").replaceAll("\\/", "\\\\/")); + } + } + return result; + } + + /** + * Add child nodes of supplied path to given parent element. Directory + * listing is done on given path. Elements representing the child nodes + * returned in the directory listing are added to the supplied parent + * element. + * + * @param doc + * XML document to which the parent node belongs + * @param parent + * parent element to add given nodes to as child elements + * @param path + * path from which to extract child nodes + * @param selectableTypes + * array of types which are the only ones that should be selectable + * in the file picker + * @param filterMimetypes + * array of MIME type patterns used to filter out child nodes + * extracted from the given path which don't match the given MIME + * type patterns + * @param facesContext + * faces context used to set image attribute on each child element + */ + private void addPathChildNodesToElement(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, String path, QName[] selectableTypes, + Pattern[] filterMimetypes, FacesContext facesContext) + { + // append elements for the child AVM nodes of the current path + // to parent element + for (final Map.Entry entry : this + .getAvmService().getDirectoryListing(-1, path).entrySet()) + { + // if AVM node is a content node and the filter MIME types parameter + // has been set, then only add child element for AVM node if it matches + // one of the specified MIME types in the parameter + if (!entry.getValue().isDirectory() && filterMimetypes.length != 0) + { + final String contentMimetype = this.getAvmService() + .getContentDataForRead(entry.getValue()).getMimetype(); + + boolean matched = false; + for (final Pattern p : filterMimetypes) + { + matched = p.matcher(contentMimetype).matches(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(p + ".matches(" + contentMimetype + ") = " + + matched); + } + if (matched) + { + break; + } + } + + // if AVM node MIME type doesn't match any of the types in the + // filter MIME types parameter, then don't do any further processing + // on it and jump back to the start of the AVM node traversal loop + if (!matched) + { + continue; + } + } + + // create child element representing AVM node and add to file picker + // data element + addAVMChildNodeToParentElement(doc, parent, entry.getValue(), + selectableTypes, facesContext); + } + } + + /** + * Add result content nodes from configured search as + * child node elements to the provided parent element + * + * @param doc + * XML document to which the supplied parent node belongs + * @param parent + * parent element to which to add search result content nodes + * as child elements + * @param configuredSearchNodeRef + * configured search node reference for which result content nodes + * are added as child elements to the provided parent element + * @param selectableTypes + * node types which must be marked as selectable in the file + * picker + * @param facesContext + * faces context used to set image attribute on each child element + */ + private void addSearchResultNodes(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, NodeRef configuredSearchNodeRef, + QName[] selectableTypes, FacesContext facesContext) + { + // run configured search to get content nodes returned in search result + List searchResultNodes = runConfiguredSearch(configuredSearchNodeRef); + + // if there are no search results (i.e. null) throw exception + if (searchResultNodes == null) + { + throw new SearcherException("No results returned by search query.\n" + + "Search node reference: " + configuredSearchNodeRef); + } + + for (AVMNodeDescriptor node : searchResultNodes) + { + // create child element representing AVM node and add to file picker + // data element + addAVMChildNodeToParentElement(doc, parent, node, selectableTypes, + facesContext); + } + } + + /** + * Run a configured search represented by the given node reference, against + * the web project the XForm is currently within + * + * @param configuredQueryNodRef + * NodeRef of the configured query with which to run the search + * + * @return content nodes returned by the search + */ + private List runConfiguredSearch( + NodeRef configSearchNodeRef) + { + // get the store id used to run the configured search query + WebProject webProject = this.getAvmBrowseBean().getWebProject(); + String storeID = webProject.getStoreId(); + + // extract the content of configured search node to XML document + ContentReader contentReader = getContentService().getReader( + configSearchNodeRef, ContentModel.PROP_CONTENT); + InputStream queryInpStream = contentReader.getContentInputStream(); + SAXReader reader = new SAXReader(); + Document queryDoc = null; + try + { + queryDoc = reader.read(queryInpStream); + } catch (DocumentException de) + { + // ignore exception and return null + return null; + } + + // extract search query from configured search XML document + String query = null; + XPath queryXPath = DocumentHelper + .createXPath(CONFIGURED_SEARCH_QUERY_XPATH); + List xpathResult = queryXPath.selectNodes(queryDoc); + if ((xpathResult != null) && (xpathResult.size() != 0)) + { + // get the text from the query element + Element queryElement = (Element) xpathResult.get(0); + String queryElemText = queryElement.getText(); + + // now extract the actual search query string from the CDATA section + // within that text + + int cdataStartDelimIndex = queryElemText.indexOf(CDATA_START_DELIM); + int cdataEndDelimIndex = queryElemText.indexOf(CDATA_END_DELIM); + + // if the CDATA start delimiter is found in the query element text + // && there is text between the CDATA start and end delimiters then + // extract + // the query string from the CDATA section + if ((cdataStartDelimIndex > -1) + && ((cdataStartDelimIndex + CDATA_START_DELIM.length()) < cdataEndDelimIndex)) + { + query = queryElemText.substring(cdataStartDelimIndex + + CDATA_START_DELIM.length(), cdataEndDelimIndex); + } + } + + // perform the search against the repository + // if query was extracted from the configured search successfully + // (extracted query non-null) + List resultNodeDescriptors = null; + if ((query != null) && (query.length() != 0)) + { + ResultSet results = null; + try + { + results = this.getSearchService().query( + new StoreRef(StoreRef.PROTOCOL_AVM, storeID), + SearchService.LANGUAGE_LUCENE, query); + + if (results.length() != 0) + { + resultNodeDescriptors = new ArrayList(); + for (int i = 0; i < results.length(); i++) + { + ResultSetRow row = results.getRow(i); + NodeRef resultNodeRef = row.getNodeRef(); + Node resultNode = new Node(resultNodeRef); + + // only add content type nodes to the search result + // as we don't want the user to navigate down into folders + // in the search results + if (getDictionaryService().isSubClass(resultNode.getType(), + ContentModel.TYPE_CONTENT)) + { + Pair pair = AVMNodeConverter + .ToAVMVersionPath(resultNodeRef); + Integer version = pair.getFirst(); + String path = pair.getSecond(); + resultNodeDescriptors.add(getAvmService().lookup(version, + path)); + } + } + } + } catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to execute search: " + + query, err); + } finally + { + if (results != null) + { + results.close(); + } + } + } + + return resultNodeDescriptors; + } + + /** + * Get the cached reference to the public saved searches folder. This method + * will first get a reference to the public saved searches folder and assign + * it to the cached reference if is it is null. + * + * @return the cached reference to the public Saved Searches folder + */ + private NodeRef getPublicSearchesRef() + { + // if the cached reference is null, then get a reference to the + // public saved searches folder to assign to it + if (publicSearchesRef == null) + { + // Use the search service get a reference to the + // public saved searches folder. + FacesContext fc = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(fc) + "/" + + Application.getGlossaryFolderName(fc) + "/" + + Application.getSavedSearchesFolderName(fc); + + List results = null; + try + { + results = getSearchService().selectNodes( + getNodeService().getRootNode(Repository.getStoreRef()), + xpath, null, getNamespaceService(), false); + } catch (AccessDeniedException err) + { + // ignore and return null + } + + if (results != null && results.size() != 0) + { + publicSearchesRef = results.get(0); + } + } + + return publicSearchesRef; + } + + /** + * Get node for configured search by name. + * + * @param configSearchName + * name of configured search for which to get node + * @return node reference for configured search + */ + public NodeRef getConfiguredSearches(String configSearchName) + { + NodeRef configSearchNodeRef = null; + + // get the folder reference from the + // public searches location + NodeRef publicSearchesFolderRef = getPublicSearchesRef(); + + // read the content nodes under the folder + List childRefs = getNodeService().getChildAssocs( + publicSearchesFolderRef, ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL); + + // return content node with name matching given configured search name + if (childRefs.size() != 0) + { + for (ChildAssociationRef ref : childRefs) + { + NodeRef childNodeRef = ref.getChildRef(); + Node childNode = new Node(childNodeRef); + if (getDictionaryService().isSubClass(childNode.getType(), + ContentModel.TYPE_CONTENT)) + { + String childNodeName = childNode.getName(); + if (childNodeName.equals(configSearchName)) + { + configSearchNodeRef = childNodeRef; + break; + } + } + } + } + + return configSearchNodeRef; + } + + /** + * Create child element representing given AVM node and add to given parent + * element + * + * @param doc + * Document to which given parent element belongs + * @param parent + * parent element to add AVM node to as child element + * @param node + * AVM node to add as child node to given parent + * @param selectableTypes + * AVM node types which must be marked as selectable + * @param facesContent + * Faces context used to get file-type icon for given AVM node + */ + private void addAVMChildNodeToParentElement(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, AVMNodeDescriptor node, + QName[] selectableTypes, FacesContext facesContext) + { + // create child node element to add to file picker data + org.w3c.dom.Element childNodeElement = doc.createElement("child-node"); + childNodeElement.setAttribute("avmPath", node.getPath()); + childNodeElement.setAttribute("webappRelativePath", AVMUtil + .getWebappRelativePath(node.getPath())); + childNodeElement.setAttribute("type", node.isDirectory() ? "directory" + : "file"); + + // Set image attribute on each child + // TODO (Glen): IS this the right image to set? + // originally from Ariel's code + childNodeElement.setAttribute("image", + (node.isDirectory() ? "/images/icons/space_small.gif" : Utils + .getFileTypeImage(facesContext, node.getName(), true))); + + boolean selectable = false; + + // set 'selectable' attribute on child node to mark whether node should + // be selectable in file picker or not + // + // TODO Ariel: faking this for now since i can't figure out how to + // efficiently get the type + // qname from the avmservice + for (final QName typeQName : selectableTypes) + { + selectable = selectable + || (WCMModel.TYPE_AVM_FOLDER.equals(typeQName) && node + .isDirectory()); + selectable = selectable + || (WCMModel.TYPE_AVM_CONTENT.equals(typeQName) && !node + .isDirectory()); + } + childNodeElement.setAttribute("selectable", Boolean.toString(selectable)); + + // append child node element to parent + parent.appendChild(childNodeElement); + } +}