diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java index b21d6577b7..3d6466399f 100644 --- a/source/java/org/alfresco/web/app/Application.java +++ b/source/java/org/alfresco/web/app/Application.java @@ -49,6 +49,7 @@ import org.alfresco.web.bean.ErrorBean; import org.alfresco.web.bean.SidebarBean; import org.alfresco.web.bean.dashboard.DashboardManager; import org.alfresco.web.bean.dialog.DialogManager; +import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; import org.alfresco.web.bean.wizard.WizardManager; import org.alfresco.web.config.ClientConfigElement; @@ -312,7 +313,9 @@ public class Application } /** - * @return Returns id of the company root + * @return Returns id of the company root + * + * @deprecated Replace with user-context-specific getCompanyRootId (e.g. could be tenant-specific) */ public static String getCompanyRootId() { @@ -323,12 +326,37 @@ public class Application * Sets the company root id. This is setup by the ContextListener. * * @param id The company root id + * + * @deprecated Replace with user-context-specific getCompanyRootId (e.g. could be tenant-specific) */ public static void setCompanyRootId(String id) { companyRootId = id; } + /** + * @return Returns id of the company root + */ + public static String getCompanyRootId(FacesContext context) + { + User user = Application.getCurrentUser(context); + if (user != null) + { + String userCompanyRootId = user.getCompanyRootId(); + if (userCompanyRootId == null) + { + userCompanyRootId = Repository.getCompanyRoot(context).getId(); + user.setCompanyRootId(userCompanyRootId); + } + + return userCompanyRootId; + } + else + { + return null; + } + } + /** * @return Returns the root path for the application */ diff --git a/source/java/org/alfresco/web/app/ContextListener.java b/source/java/org/alfresco/web/app/ContextListener.java index 44c5c208d7..222e27691e 100644 --- a/source/java/org/alfresco/web/app/ContextListener.java +++ b/source/java/org/alfresco/web/app/ContextListener.java @@ -25,7 +25,6 @@ package org.alfresco.web.app; import java.util.Enumeration; -import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -49,8 +48,6 @@ import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.bean.repository.User; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.quartz.Scheduler; -import org.quartz.SchedulerException; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; @@ -93,33 +90,14 @@ public class ContextListener implements ServletContextListener, HttpSessionListe tx.begin(); authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - // get and setup the initial store ref from config + // get and setup the initial store ref and root path from config StoreRef storeRef = Repository.getStoreRef(servletContext); - - // check the repository exists, create if it doesn't - if (nodeService.exists(storeRef) == false) - { - throw new AlfrescoRuntimeException("Store not created prior to application startup: " + storeRef); - } - - // get hold of the root node - NodeRef rootNodeRef = nodeService.getRootNode(storeRef); - - // see if the company home space is present + + // get root path String rootPath = Application.getRootPath(servletContext); - if (rootPath == null) - { - throw new AlfrescoRuntimeException("Root path has not been configured"); - } - - List nodes = searchService.selectNodes(rootNodeRef, rootPath, null, namespaceService, false); - if (nodes.size() == 0) - { - throw new AlfrescoRuntimeException("Root path not created prior to application startup: " + rootPath); - } // Extract company space id and store it in the Application object - companySpaceNodeRef = nodes.get(0); + companySpaceNodeRef = Repository.getCompanyRoot(nodeService, searchService, namespaceService, storeRef, rootPath); Application.setCompanyRootId(companySpaceNodeRef.getId()); // commit the transaction diff --git a/source/java/org/alfresco/web/bean/BrowseBean.java b/source/java/org/alfresco/web/bean/BrowseBean.java index 46fe89d4f0..02fe46edfa 100644 --- a/source/java/org/alfresco/web/bean/BrowseBean.java +++ b/source/java/org/alfresco/web/bean/BrowseBean.java @@ -1371,10 +1371,11 @@ public class BrowseBean implements IContextListener Node node = getActionSpace(); if (node != null) { - NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + FacesContext fc = FacesContext.getCurrentInstance(); + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); if (node.getNodeRef().equals(companyRootRef)) { - message = Application.getMessage(FacesContext.getCurrentInstance(), MSG_DELETE_COMPANYROOT); + message = Application.getMessage(fc, MSG_DELETE_COMPANYROOT); } } diff --git a/source/java/org/alfresco/web/bean/NavigationBean.java b/source/java/org/alfresco/web/bean/NavigationBean.java index 2a20d6a5cd..c79ba2715b 100644 --- a/source/java/org/alfresco/web/bean/NavigationBean.java +++ b/source/java/org/alfresco/web/bean/NavigationBean.java @@ -574,10 +574,11 @@ public class NavigationBean } catch (InvalidNodeRefException refErr) { + FacesContext fc = FacesContext.getCurrentInstance(); Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), ERROR_DELETED_FOLDER), new Object[] {this.currentNodeId}) ); + fc, ERROR_DELETED_FOLDER), new Object[] {this.currentNodeId}) ); - nodeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + nodeRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); node = new Node(nodeRef); props = node.getProperties(); } @@ -679,8 +680,13 @@ public class NavigationBean { if (this.companyHomeNode == null) { - NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); - this.companyHomeNode = new Node(companyRootRef); + FacesContext fc = FacesContext.getCurrentInstance(); + String companyRootId = Application.getCompanyRootId(fc); + if (companyRootId != null) + { + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), companyRootId); + this.companyHomeNode = new Node(companyRootRef); + } } return this.companyHomeNode; } @@ -721,7 +727,15 @@ public class NavigationBean */ public boolean getCompanyHomeVisible() { - return getCompanyHomeNode().hasPermission(PermissionService.READ); + Node companyHomeNode = getCompanyHomeNode(); + if (companyHomeNode != null) + { + return companyHomeNode.hasPermission(PermissionService.READ); + } + else + { + return false; + } } /** diff --git a/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java b/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java index 4e7f1c60cc..cf0928e59f 100644 --- a/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java +++ b/source/java/org/alfresco/web/bean/ajax/NavigatorPluginBean.java @@ -321,12 +321,12 @@ public class NavigatorPluginBean implements IContextListener UserTransaction tx = null; try { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + FacesContext fc = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(fc, true); tx.begin(); // query for the child nodes of company home - NodeRef root = new NodeRef(Repository.getStoreRef(), - Application.getCompanyRootId()); + NodeRef root = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(fc)); List childRefs = this.nodeService.getChildAssocs(root, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); diff --git a/source/java/org/alfresco/web/bean/repository/Repository.java b/source/java/org/alfresco/web/bean/repository/Repository.java index 17e95643f0..fd1b729afc 100644 --- a/source/java/org/alfresco/web/bean/repository/Repository.java +++ b/source/java/org/alfresco/web/bean/repository/Repository.java @@ -1,668 +1,732 @@ -/* - * 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.repository; - -import java.io.InputStream; -import java.io.Serializable; -import java.nio.charset.Charset; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.faces.context.FacesContext; -import javax.servlet.ServletContext; -import javax.transaction.UserTransaction; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.configuration.ConfigurableService; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.encoding.ContentCharsetFinder; -import org.alfresco.repo.content.metadata.MetadataExtracter; -import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.lock.LockStatus; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.web.app.Application; -import org.alfresco.web.ui.common.Utils; -import org.apache.log4j.Logger; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.web.jsf.FacesContextUtils; - -/** - * Helper class for accessing repository objects, convert values, escape values and service utilities. - * - * @author gavinc - * @author kevinr - */ -public final class Repository -{ - /** I18N error messages */ - public static final String ERROR_NODEREF = "error_noderef"; - public static final String ERROR_GENERIC = "error_generic"; - public static final String ERROR_NOHOME = "error_homespace"; - public static final String ERROR_SEARCH = "error_search"; - public static final String ERROR_EXISTS = "error_exists"; - - private static final String METADATA_EXTACTER_REGISTRY = "metadataExtracterRegistry"; - - private static Logger logger = Logger.getLogger(Repository.class); - - /** cache of client StoreRef */ - private static StoreRef storeRef = null; - - /** reference to the NamespaceService */ - private static NamespaceService namespaceService = null; - - /** reference to the ServiceRegistry */ - private static ServiceRegistry serviceRegistry = null; - - /** - * Private constructor - */ - private Repository() - { - } - - /** - * Returns a store reference object - * - * @return A StoreRef object - */ - public static StoreRef getStoreRef() - { - return storeRef; - } - - /** - * Returns a store reference object. - * This method is used to setup the cached value by the ContextListener initialisation methods - * - * @return The StoreRef object - */ - public static StoreRef getStoreRef(ServletContext context) - { - storeRef = Application.getRepositoryStoreRef(context); - - return storeRef; - } - - /** - * Helper to get the display name for a Node. - * The method will attempt to use the "name" attribute, if not found it will revert to using - * the QName.getLocalName() retrieved from the primary parent relationship. - * - * @param ref NodeRef - * - * @return display name string for the specified Node. - */ - public static String getNameForNode(NodeService nodeService, NodeRef ref) - { - String name = null; - - // try to find a display "name" property for this node - Object nameProp = nodeService.getProperty(ref, ContentModel.PROP_NAME); - if (nameProp != null) - { - name = nameProp.toString(); - } - else - { - // revert to using QName if not found - QName qname = nodeService.getPrimaryParent(ref).getQName(); - if (qname != null) - { - name = qname.getLocalName(); - } - } - - return name; - } - - /** - * Escape a QName value so it can be used in lucene search strings - * - * @param qName QName to escape - * - * @return escaped value - */ - public static String escapeQName(QName qName) - { - String string = qName.toString(); - StringBuilder buf = new StringBuilder(string.length() + 4); - for (int i = 0; i < string.length(); i++) - { - char c = string.charAt(i); - if ((c == '{') || (c == '}') || (c == ':') || (c == '-')) - { - buf.append('\\'); - } - - buf.append(c); - } - return buf.toString(); - } - - /** - * Return whether a Node is currently locked - * - * @param node The Node wrapper to test against - * @param lockService The LockService to use - * - * @return whether a Node is currently locked - */ - public static Boolean isNodeLocked(Node node, LockService lockService) - { - Boolean locked = Boolean.FALSE; - - if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) - { - LockStatus lockStatus = lockService.getLockStatus(node.getNodeRef()); - if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) - { - locked = Boolean.TRUE; - } - } - - return locked; - } - - /** - * Return whether a Node is currently locked by the current user - * - * @param node The Node wrapper to test against - * @param lockService The LockService to use - * - * @return whether a Node is currently locked by the current user - */ - public static Boolean isNodeOwnerLocked(Node node, LockService lockService) - { - Boolean locked = Boolean.FALSE; - - if (node.hasAspect(ContentModel.ASPECT_LOCKABLE) && - lockService.getLockStatus(node.getNodeRef()) == LockStatus.LOCK_OWNER) - { - locked = Boolean.TRUE; - } - - return locked; - } - - /** - * Return the human readable form of the specified node Path. Fast version of the method that - * simply converts QName localname components to Strings. - * - * @param path Path to extract readable form from, excluding the final element - * - * @return human readable form of the Path excluding the final element - */ - public static String getDisplayPath(Path path) - { - return getDisplayPath(path, false); - } - - /** - * Return the human readable form of the specified node Path. Fast version of the method that - * simply converts QName localname components to Strings. - * - * @param path Path to extract readable form from - * @param showLeaf Whether to process the final leaf element of the path - * - * @return human readable form of the Path excluding the final element - */ - public static String getDisplayPath(Path path, boolean showLeaf) - { - StringBuilder buf = new StringBuilder(64); - - int count = path.size() - (showLeaf ? 0 : 1); - for (int i=0; i - * The file extension will be extracted from the filename and used to lookup the mimetype. - * - * @param context FacesContext - * @param filename Non-null filename to process - * - * @return mimetype for the specified filename - falls back to 'application/octet-stream' if not found. - */ - public static String getMimeTypeForFileName(FacesContext context, String filename) - { - // base the mimetype from the file extension - MimetypeService mimetypeService = (MimetypeService)getServiceRegistry(context).getMimetypeService(); - - // fall back to binary mimetype if no match found - String mimetype = MimetypeMap.MIMETYPE_BINARY; - int extIndex = filename.lastIndexOf('.'); - if (extIndex != -1) - { - String ext = filename.substring(extIndex + 1).toLowerCase(); - String mt = mimetypeService.getMimetypesByExtension().get(ext); - if (mt != null) - { - mimetype = mt; - } - } - - return mimetype; - } - - /** - * Return a UserTransaction instance - * - * @param context FacesContext - * - * @return UserTransaction - * - * @deprecated - * @see #getRetryingTransactionHelper(FacesContext) - */ - public static UserTransaction getUserTransaction(FacesContext context) - { - TransactionService transactionService = getServiceRegistry(context).getTransactionService(); - return transactionService.getUserTransaction(); - } - - /** - * Returns the transaction helper that executes a unit of work. - * - * @param context FacesContext - * @return Returns the transaction helper - */ - public static RetryingTransactionHelper getRetryingTransactionHelper(FacesContext context) - { - TransactionService transactionService = getServiceRegistry(context).getTransactionService(); - return transactionService.getRetryingTransactionHelper(); - } - - /** - * Return a UserTransaction instance - * - * @param context FacesContext - * @param readonly Transaction readonly state - * - * @return UserTransaction - */ - public static UserTransaction getUserTransaction(FacesContext context, boolean readonly) - { - TransactionService transactionService = getServiceRegistry(context).getTransactionService(); - return transactionService.getUserTransaction(readonly); - } - - /** - * Return the Repository Service Registry - * - * @param context Faces Context - * @return the Service Registry - */ - public static ServiceRegistry getServiceRegistry(FacesContext context) - { - if (serviceRegistry == null) - { - serviceRegistry = (ServiceRegistry)FacesContextUtils.getRequiredWebApplicationContext( - context).getBean(ServiceRegistry.SERVICE_REGISTRY); - } - return serviceRegistry; - } - - /** - * Return the Repository Service Registry - * - * @param context Servlet Context - * @return the Service Registry - */ - public static ServiceRegistry getServiceRegistry(ServletContext context) - { - if (serviceRegistry == null) - { - serviceRegistry = (ServiceRegistry)WebApplicationContextUtils.getRequiredWebApplicationContext( - context).getBean(ServiceRegistry.SERVICE_REGISTRY); - } - return serviceRegistry; - } - - /** - * Return the Configurable Service - * - * @return the configurable service - */ - public static ConfigurableService getConfigurableService(FacesContext context) - { - return (ConfigurableService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("ConfigurableService"); - } - - /** - * Return the Metadata Extracter Registry - * - * @param context Faces Context - * @return the MetadataExtracterRegistry - */ - public static MetadataExtracterRegistry getMetadataExtracterRegistry(FacesContext context) - { - return (MetadataExtracterRegistry)FacesContextUtils.getRequiredWebApplicationContext( - context).getBean(METADATA_EXTACTER_REGISTRY); - } - - /** - * Extracts the metadata of a "raw" piece of content into a map. - * - * @param context Faces Context - * @param reader Content reader for the source content to extract from - * @param destination Map of metadata to set metadata values into - * @return True if an extracter was found - */ - public static boolean extractMetadata(FacesContext context, ContentReader reader, Map destination) - { - // check that source mimetype is available - String mimetype = reader.getMimetype(); - if (mimetype == null) - { - throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); - } - - // look for a transformer - MetadataExtracter extracter = getMetadataExtracterRegistry(context).getExtracter(mimetype); - if (extracter == null) - { - // No metadata extracter is not a failure, but we flag it - return false; - } - - try - { - // we have a transformer, so do it - extracter.extract(reader, destination); - return true; - } - catch (Throwable e) - { - // it failed - logger.warn("Metadata extraction failed: \n" + - " reader: " + reader + "\n" + - " extracter: " + extracter); - return false; - } - } - - /** - * Extract the characterset from the stream - * - * @param context the Faces Context - * @param is the stream of characters or data - * @param mimetype the stream's mimetype, or null if unknown - * @return Returns the guessed characterset and never null - */ - public static String guessEncoding(FacesContext context, InputStream is, String mimetype) - { - ContentCharsetFinder charsetFinder = getServiceRegistry(context).getMimetypeService().getContentCharsetFinder(); - Charset charset = charsetFinder.getCharset(is, mimetype); - return charset.name(); - } - - /** - * Query a list of Person type nodes from the repo - * It is currently assumed that all Person nodes exist below the Repository root node - * - * @param context Faces Context - * @param nodeService The node service - * @param searchService used to perform the search - * @return List of Person node objects - */ - public static List getUsers(FacesContext context, NodeService nodeService, SearchService searchService) - { - List personNodes = null; - - UserTransaction tx = null; - try - { - tx = Repository.getUserTransaction(context, true); - tx.begin(); - - PersonService personService = (PersonService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("personService"); - NodeRef peopleRef = personService.getPeopleContainer(); - - // TODO: better to perform an XPath search or a get for a specific child type here? - List childRefs = nodeService.getChildAssocs(peopleRef); - personNodes = new ArrayList(childRefs.size()); - for (ChildAssociationRef ref: childRefs) - { - // create our Node representation from the NodeRef - NodeRef nodeRef = ref.getChildRef(); - - if (nodeService.getType(nodeRef).equals(ContentModel.TYPE_PERSON)) - { - // create our Node representation - MapNode node = new MapNode(nodeRef); - - // set data binding properties - // this will also force initialisation of the props now during the UserTransaction - // it is much better for performance to do this now rather than during page bind - Map props = node.getProperties(); - String lastName = (String)props.get("lastName"); - props.put("fullName", ((String)props.get("firstName")) + ' ' + (lastName != null ? lastName : "")); - NodeRef homeFolderNodeRef = (NodeRef)props.get("homeFolder"); - if (homeFolderNodeRef != null) - { - props.put("homeSpace", homeFolderNodeRef); - } - - personNodes.add(node); - } - } - - // commit the transaction - tx.commit(); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - context, Repository.ERROR_NODEREF), new Object[] {"root"}) ); - personNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - catch (Throwable err) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - context, Repository.ERROR_GENERIC), err.getMessage()), err ); - personNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - - return personNodes; - } - - /** - * Convert a property of unknown type to a String value. A native String value will be - * returned directly, else toString() will be executed, null is returned as null. - * - * @param value Property value - * - * @return value to String or null - */ - public static String safePropertyToString(Serializable value) - { - if (value == null) - { - return null; - } - else if (value instanceof String) - { - return (String)value; - } - else - { - return value.toString(); - } - } - - /** - * Creates a QName representation for the given String. - * If the String has no namespace the Alfresco namespace is added. - * If the String has a prefix an attempt to resolve the prefix to the - * full URI will be made. - * - * @param str The string to convert - * @return A QName representation of the given string - */ - public static QName resolveToQName(String str) - { - return QName.resolveToQName(getNamespaceService(), str); - } - - /** - * Creates a string representation of a QName for the given string. - * If the given string already has a namespace, either a URL or a prefix, - * nothing the given string is returned. If it does not have a namespace - * the Alfresco namespace is added. - * - * @param str The string to convert - * @return A QName String representation of the given string - */ - public static String resolveToQNameString(String str) - { - return QName.resolveToQNameString(getNamespaceService(), str); - } - - /** - * Returns an instance of the namespace service - * - * @return The NamespaceService - */ - private static NamespaceService getNamespaceService() - { - if (namespaceService == null) - { - ServiceRegistry svcReg = getServiceRegistry(FacesContext.getCurrentInstance()); - namespaceService = svcReg.getNamespaceService(); - } - - return namespaceService; - } -} +/* + * 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.repository; + +import java.io.InputStream; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.servlet.ServletContext; +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.configuration.ConfigurableService; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.content.metadata.MetadataExtracter; +import org.alfresco.repo.content.metadata.MetadataExtracterRegistry; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.Utils; +import org.apache.log4j.Logger; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Helper class for accessing repository objects, convert values, escape values and service utilities. + * + * @author gavinc + * @author kevinr + */ +public final class Repository +{ + /** I18N error messages */ + public static final String ERROR_NODEREF = "error_noderef"; + public static final String ERROR_GENERIC = "error_generic"; + public static final String ERROR_NOHOME = "error_homespace"; + public static final String ERROR_SEARCH = "error_search"; + public static final String ERROR_EXISTS = "error_exists"; + + private static final String METADATA_EXTACTER_REGISTRY = "metadataExtracterRegistry"; + + private static Logger logger = Logger.getLogger(Repository.class); + + /** cache of client StoreRef */ + private static StoreRef storeRef = null; + + /** reference to the NamespaceService */ + private static NamespaceService namespaceService = null; + + /** reference to the ServiceRegistry */ + private static ServiceRegistry serviceRegistry = null; + + /** + * Private constructor + */ + private Repository() + { + } + + /** + * Returns a store reference object + * + * @return A StoreRef object + */ + public static StoreRef getStoreRef() + { + return storeRef; + } + + /** + * Returns a store reference object. + * This method is used to setup the cached value by the ContextListener initialisation methods + * + * @return The StoreRef object + */ + public static StoreRef getStoreRef(ServletContext context) + { + storeRef = Application.getRepositoryStoreRef(context); + + return storeRef; + } + + /** + * Returns a company root node reference object. + * + * @return The NodeRef object + */ + public static NodeRef getCompanyRoot(final FacesContext context) + { + // note: run in context of System user using tenant-specific store + // so that Company Root can be returned, even if the user does not have + // permission to access the Company Root (including, for example, the Guest user) + return AuthenticationUtil.runAs(new RunAsWork() + { + public NodeRef doWork() throws Exception + { + ServiceRegistry sr = getServiceRegistry(context); + + TenantService tenantService = (TenantService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("tenantService"); + + // get store ref (from config) + StoreRef storeRef = tenantService.getName(Repository.getStoreRef()); + + // get root path (from config) + String rootPath = Application.getRootPath(context); + + return getCompanyRoot(sr.getNodeService(), sr.getSearchService(), sr.getNamespaceService(), storeRef, rootPath); + } + }, AuthenticationUtil.getSystemUserName()); + } + + /** + * Returns a company root node reference object. + * + * @return The NodeRef object + */ + public static NodeRef getCompanyRoot(NodeService nodeService, SearchService searchService, NamespaceService namespaceService, StoreRef storeRef, String rootPath) + { + // check the repository exists, create if it doesn't + if (nodeService.exists(storeRef) == false) + { + throw new AlfrescoRuntimeException("Store not created prior to application startup: " + storeRef); + } + + // get hold of the root node + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + + // see if the company home space is present + if (rootPath == null) + { + throw new AlfrescoRuntimeException("Root path has not been configured"); + } + + List nodes = searchService.selectNodes(rootNodeRef, rootPath, null, namespaceService, false); + if (nodes.size() == 0) + { + throw new AlfrescoRuntimeException("Root path not created prior to application startup: " + rootPath); + } + + // return company root + return nodes.get(0); + } + + /** + * Helper to get the display name for a Node. + * The method will attempt to use the "name" attribute, if not found it will revert to using + * the QName.getLocalName() retrieved from the primary parent relationship. + * + * @param ref NodeRef + * + * @return display name string for the specified Node. + */ + public static String getNameForNode(NodeService nodeService, NodeRef ref) + { + String name = null; + + // try to find a display "name" property for this node + Object nameProp = nodeService.getProperty(ref, ContentModel.PROP_NAME); + if (nameProp != null) + { + name = nameProp.toString(); + } + else + { + // revert to using QName if not found + QName qname = nodeService.getPrimaryParent(ref).getQName(); + if (qname != null) + { + name = qname.getLocalName(); + } + } + + return name; + } + + /** + * Escape a QName value so it can be used in lucene search strings + * + * @param qName QName to escape + * + * @return escaped value + */ + public static String escapeQName(QName qName) + { + String string = qName.toString(); + StringBuilder buf = new StringBuilder(string.length() + 4); + for (int i = 0; i < string.length(); i++) + { + char c = string.charAt(i); + if ((c == '{') || (c == '}') || (c == ':') || (c == '-')) + { + buf.append('\\'); + } + + buf.append(c); + } + return buf.toString(); + } + + /** + * Return whether a Node is currently locked + * + * @param node The Node wrapper to test against + * @param lockService The LockService to use + * + * @return whether a Node is currently locked + */ + public static Boolean isNodeLocked(Node node, LockService lockService) + { + Boolean locked = Boolean.FALSE; + + if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) + { + LockStatus lockStatus = lockService.getLockStatus(node.getNodeRef()); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = Boolean.TRUE; + } + } + + return locked; + } + + /** + * Return whether a Node is currently locked by the current user + * + * @param node The Node wrapper to test against + * @param lockService The LockService to use + * + * @return whether a Node is currently locked by the current user + */ + public static Boolean isNodeOwnerLocked(Node node, LockService lockService) + { + Boolean locked = Boolean.FALSE; + + if (node.hasAspect(ContentModel.ASPECT_LOCKABLE) && + lockService.getLockStatus(node.getNodeRef()) == LockStatus.LOCK_OWNER) + { + locked = Boolean.TRUE; + } + + return locked; + } + + /** + * Return the human readable form of the specified node Path. Fast version of the method that + * simply converts QName localname components to Strings. + * + * @param path Path to extract readable form from, excluding the final element + * + * @return human readable form of the Path excluding the final element + */ + public static String getDisplayPath(Path path) + { + return getDisplayPath(path, false); + } + + /** + * Return the human readable form of the specified node Path. Fast version of the method that + * simply converts QName localname components to Strings. + * + * @param path Path to extract readable form from + * @param showLeaf Whether to process the final leaf element of the path + * + * @return human readable form of the Path excluding the final element + */ + public static String getDisplayPath(Path path, boolean showLeaf) + { + StringBuilder buf = new StringBuilder(64); + + int count = path.size() - (showLeaf ? 0 : 1); + for (int i=0; i + * The file extension will be extracted from the filename and used to lookup the mimetype. + * + * @param context FacesContext + * @param filename Non-null filename to process + * + * @return mimetype for the specified filename - falls back to 'application/octet-stream' if not found. + */ + public static String getMimeTypeForFileName(FacesContext context, String filename) + { + // base the mimetype from the file extension + MimetypeService mimetypeService = (MimetypeService)getServiceRegistry(context).getMimetypeService(); + + // fall back to binary mimetype if no match found + String mimetype = MimetypeMap.MIMETYPE_BINARY; + int extIndex = filename.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = filename.substring(extIndex + 1).toLowerCase(); + String mt = mimetypeService.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + + return mimetype; + } + + /** + * Return a UserTransaction instance + * + * @param context FacesContext + * + * @return UserTransaction + * + * @deprecated + * @see #getRetryingTransactionHelper(FacesContext) + */ + public static UserTransaction getUserTransaction(FacesContext context) + { + TransactionService transactionService = getServiceRegistry(context).getTransactionService(); + return transactionService.getUserTransaction(); + } + + /** + * Returns the transaction helper that executes a unit of work. + * + * @param context FacesContext + * @return Returns the transaction helper + */ + public static RetryingTransactionHelper getRetryingTransactionHelper(FacesContext context) + { + TransactionService transactionService = getServiceRegistry(context).getTransactionService(); + return transactionService.getRetryingTransactionHelper(); + } + + /** + * Return a UserTransaction instance + * + * @param context FacesContext + * @param readonly Transaction readonly state + * + * @return UserTransaction + */ + public static UserTransaction getUserTransaction(FacesContext context, boolean readonly) + { + TransactionService transactionService = getServiceRegistry(context).getTransactionService(); + return transactionService.getUserTransaction(readonly); + } + + /** + * Return the Repository Service Registry + * + * @param context Faces Context + * @return the Service Registry + */ + public static ServiceRegistry getServiceRegistry(FacesContext context) + { + if (serviceRegistry == null) + { + serviceRegistry = (ServiceRegistry)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean(ServiceRegistry.SERVICE_REGISTRY); + } + return serviceRegistry; + } + + /** + * Return the Repository Service Registry + * + * @param context Servlet Context + * @return the Service Registry + */ + public static ServiceRegistry getServiceRegistry(ServletContext context) + { + if (serviceRegistry == null) + { + serviceRegistry = (ServiceRegistry)WebApplicationContextUtils.getRequiredWebApplicationContext( + context).getBean(ServiceRegistry.SERVICE_REGISTRY); + } + return serviceRegistry; + } + + /** + * Return the Configurable Service + * + * @return the configurable service + */ + public static ConfigurableService getConfigurableService(FacesContext context) + { + return (ConfigurableService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("ConfigurableService"); + } + + /** + * Return the Metadata Extracter Registry + * + * @param context Faces Context + * @return the MetadataExtracterRegistry + */ + public static MetadataExtracterRegistry getMetadataExtracterRegistry(FacesContext context) + { + return (MetadataExtracterRegistry)FacesContextUtils.getRequiredWebApplicationContext( + context).getBean(METADATA_EXTACTER_REGISTRY); + } + + /** + * Extracts the metadata of a "raw" piece of content into a map. + * + * @param context Faces Context + * @param reader Content reader for the source content to extract from + * @param destination Map of metadata to set metadata values into + * @return True if an extracter was found + */ + public static boolean extractMetadata(FacesContext context, ContentReader reader, Map destination) + { + // check that source mimetype is available + String mimetype = reader.getMimetype(); + if (mimetype == null) + { + throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); + } + + // look for a transformer + MetadataExtracter extracter = getMetadataExtracterRegistry(context).getExtracter(mimetype); + if (extracter == null) + { + // No metadata extracter is not a failure, but we flag it + return false; + } + + try + { + // we have a transformer, so do it + extracter.extract(reader, destination); + return true; + } + catch (Throwable e) + { + // it failed + logger.warn("Metadata extraction failed: \n" + + " reader: " + reader + "\n" + + " extracter: " + extracter); + return false; + } + } + + /** + * Extract the characterset from the stream + * + * @param context the Faces Context + * @param is the stream of characters or data + * @param mimetype the stream's mimetype, or null if unknown + * @return Returns the guessed characterset and never null + */ + public static String guessEncoding(FacesContext context, InputStream is, String mimetype) + { + ContentCharsetFinder charsetFinder = getServiceRegistry(context).getMimetypeService().getContentCharsetFinder(); + Charset charset = charsetFinder.getCharset(is, mimetype); + return charset.name(); + } + + /** + * Query a list of Person type nodes from the repo + * It is currently assumed that all Person nodes exist below the Repository root node + * + * @param context Faces Context + * @param nodeService The node service + * @param searchService used to perform the search + * @return List of Person node objects + */ + public static List getUsers(FacesContext context, NodeService nodeService, SearchService searchService) + { + List personNodes = null; + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + PersonService personService = (PersonService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean("personService"); + NodeRef peopleRef = personService.getPeopleContainer(); + + // TODO: better to perform an XPath search or a get for a specific child type here? + List childRefs = nodeService.getChildAssocs(peopleRef); + personNodes = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref: childRefs) + { + // create our Node representation from the NodeRef + NodeRef nodeRef = ref.getChildRef(); + + if (nodeService.getType(nodeRef).equals(ContentModel.TYPE_PERSON)) + { + // create our Node representation + MapNode node = new MapNode(nodeRef); + + // set data binding properties + // this will also force initialisation of the props now during the UserTransaction + // it is much better for performance to do this now rather than during page bind + Map props = node.getProperties(); + String lastName = (String)props.get("lastName"); + props.put("fullName", ((String)props.get("firstName")) + ' ' + (lastName != null ? lastName : "")); + NodeRef homeFolderNodeRef = (NodeRef)props.get("homeFolder"); + if (homeFolderNodeRef != null) + { + props.put("homeSpace", homeFolderNodeRef); + } + + personNodes.add(node); + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_NODEREF), new Object[] {"root"}) ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + context, Repository.ERROR_GENERIC), err.getMessage()), err ); + personNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + return personNodes; + } + + /** + * Convert a property of unknown type to a String value. A native String value will be + * returned directly, else toString() will be executed, null is returned as null. + * + * @param value Property value + * + * @return value to String or null + */ + public static String safePropertyToString(Serializable value) + { + if (value == null) + { + return null; + } + else if (value instanceof String) + { + return (String)value; + } + else + { + return value.toString(); + } + } + + /** + * Creates a QName representation for the given String. + * If the String has no namespace the Alfresco namespace is added. + * If the String has a prefix an attempt to resolve the prefix to the + * full URI will be made. + * + * @param str The string to convert + * @return A QName representation of the given string + */ + public static QName resolveToQName(String str) + { + return QName.resolveToQName(getNamespaceService(), str); + } + + /** + * Creates a string representation of a QName for the given string. + * If the given string already has a namespace, either a URL or a prefix, + * nothing the given string is returned. If it does not have a namespace + * the Alfresco namespace is added. + * + * @param str The string to convert + * @return A QName String representation of the given string + */ + public static String resolveToQNameString(String str) + { + return QName.resolveToQNameString(getNamespaceService(), str); + } + + /** + * Returns an instance of the namespace service + * + * @return The NamespaceService + */ + private static NamespaceService getNamespaceService() + { + if (namespaceService == null) + { + ServiceRegistry svcReg = getServiceRegistry(FacesContext.getCurrentInstance()); + namespaceService = svcReg.getNamespaceService(); + } + + return namespaceService; + } +} diff --git a/source/java/org/alfresco/web/bean/repository/User.java b/source/java/org/alfresco/web/bean/repository/User.java index 92e8380a32..f330d3d60c 100644 --- a/source/java/org/alfresco/web/bean/repository/User.java +++ b/source/java/org/alfresco/web/bean/repository/User.java @@ -49,6 +49,7 @@ import org.alfresco.web.app.Application; */ public final class User { + private String companyRootId; private String homeSpaceId; private String userName; private String ticket; @@ -128,6 +129,22 @@ public final class User this.homeSpaceId = homeSpaceId; } + /** + * @return Retrieves the company home space + */ + public String getCompanyRootId() + { + return this.companyRootId; + } + + /** + * @param companyRootId Sets the id of the company home space + */ + public void setCompanyRootId(String companyRootId) + { + this.companyRootId = companyRootId; + } + /** * @return Returns the ticket. */ diff --git a/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java index dd262f4164..7fd5640f1b 100644 --- a/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java +++ b/source/java/org/alfresco/web/bean/wizard/NewUserWizard.java @@ -41,6 +41,8 @@ import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; @@ -101,6 +103,9 @@ public class NewUserWizard extends AbstractWizardBean /** PersonService bean reference */ private PersonService personService; + + /** TenantService bean reference */ + private TenantService tenantService; /** OwnableService bean reference */ private OwnableService ownableService; @@ -154,6 +159,14 @@ public class NewUserWizard extends AbstractWizardBean { this.ownableService = ownableService; } + + /** + * @param tenantService The tenantService to set. + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } /** * Initialises the wizard @@ -474,6 +487,31 @@ public class NewUserWizard extends AbstractWizardBean } else { + if (tenantService.isEnabled()) + { + String currentDomain = tenantService.getCurrentUserDomain(); + if (currentDomain != null) + { + if (! tenantService.isTenantUser(this.userName)) + { + // force domain onto the end of the username + this.userName = tenantService.getDomainUser(this.userName, currentDomain); + logger.warn("Added domain to username: " + this.userName); + } + else + { + try + { + tenantService.checkDomainUser(this.userName); + } + catch (RuntimeException re) + { + throw new AuthenticationException("User must belong to same domain as admin: " + currentDomain); + } + } + } + } + if (this.password.equals(this.confirm)) { // create properties for Person type from submitted Form data @@ -840,18 +878,7 @@ public class NewUserWizard extends AbstractWizardBean { if (this.companyHomeSpaceRef == null) { - String companyXPath = Application.getRootPath(FacesContext.getCurrentInstance()); - - NodeRef rootNodeRef = this.nodeService.getRootNode(Repository.getStoreRef()); - List nodes = this.searchService.selectNodes(rootNodeRef, companyXPath, null, this.namespaceService, - false); - - if (nodes.size() == 0) - { - throw new IllegalStateException("Unable to find company home space path: " + companyXPath); - } - - this.companyHomeSpaceRef = nodes.get(0); + this.companyHomeSpaceRef = Repository.getCompanyRoot(FacesContext.getCurrentInstance()); } return this.companyHomeSpaceRef; diff --git a/source/java/org/alfresco/web/ui/repo/component/UISpaceSelector.java b/source/java/org/alfresco/web/ui/repo/component/UISpaceSelector.java index ff615cbe67..c61368bd75 100644 --- a/source/java/org/alfresco/web/ui/repo/component/UISpaceSelector.java +++ b/source/java/org/alfresco/web/ui/repo/component/UISpaceSelector.java @@ -66,7 +66,7 @@ public class UISpaceSelector extends AbstractItemSelector { String id = null; - if (this.navigationId != null && this.navigationId.equals(Application.getCompanyRootId()) == false) + if (this.navigationId != null && this.navigationId.equals(Application.getCompanyRootId(context)) == false) { try { @@ -107,7 +107,7 @@ public class UISpaceSelector extends AbstractItemSelector public Collection getRootChildren(FacesContext context) { - NodeRef rootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + NodeRef rootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId(context)); // get a child association reference back from the parent node to satisfy // the generic API we have in the abstract super class