From facbdaf5b76e6dceaac0d95e9437a3dd62ec7ce4 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 23 Mar 2009 14:01:29 +0000 Subject: [PATCH] MOB-412: Decouple thread local authentication methods from AuthenticationComponent into new AuthenticationContext super-interface. The AuthenticationContext is a delegate of AbstractAuthenticationComponent and can be accessed directly by low-level classes (e.g. schema bootstrap) before the authentication subsystem is available. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13721 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 12 +- .../authentication-services-context.xml | 6 + config/alfresco/import-export-context.xml | 8 +- .../linkvalidation-service-context.xml | 4 +- config/alfresco/mt/mt-base-context.xml | 2 +- config/alfresco/network-protocol-context.xml | 2 +- .../alfresco/patch/patch-services-context.xml | 4 +- .../alfresco/preference-service-context.xml | 2 +- config/alfresco/site-services-context.xml | 2 +- config/alfresco/usage-services-context.xml | 2 +- config/alfresco/workflow-context.xml | 2 +- .../filesys/repo/ContentDiskDriver.java | 5342 ++++++++--------- .../alfresco/filesys/repo/NodeMonitor.java | 6 +- .../java/org/alfresco/jcr/test/TestData.java | 296 +- .../repo/action/ActionServiceImpl.java | 3194 +++++----- .../AsynchronousActionExecutionQueueImpl.java | 10 +- .../repo/admin/patch/AbstractPatch.java | 1116 ++-- .../alfresco/repo/admin/patch/PatchTest.java | 397 +- .../impl/EmailTemplatesContentPatch.java | 2 +- .../admin/patch/impl/ScriptsFolderPatch.java | 2 +- .../org/alfresco/repo/avm/AVMNodeImpl.java | 8 +- .../org/alfresco/repo/avm/AVMStoreImpl.java | 3752 ++++++------ .../alfresco/repo/avm/util/RawServices.java | 16 +- .../repo/config/xml/RepoXMLConfigService.java | 1026 ++-- .../repo/importer/ExportSourceImporter.java | 515 +- .../repo/importer/FileSourceImporter.java | 484 +- .../repo/importer/ImporterBootstrap.java | 1436 ++--- .../importer/system/SystemInfoBootstrap.java | 16 +- .../repo/module/ModuleComponentHelper.java | 1323 ++-- .../repo/module/ModuleServiceImpl.java | 418 +- .../preference/PreferenceServiceImpl.java | 738 +-- .../AbstractAuthenticationComponent.java | 798 +-- .../AuthenticationComponent.java | 53 +- .../authentication/AuthenticationContext.java | 122 + .../AuthenticationContextImpl.java | 144 + .../ChainingAuthenticationComponentImpl.java | 165 +- .../authority/AuthorityServiceImpl.java | 768 ++- .../authority/SimpleAuthorityServiceImpl.java | 574 +- .../impl/PermissionServiceImpl.java | 3883 ++++++------ .../alfresco/repo/site/SiteServiceImpl.java | 3056 +++++----- .../repo/tenant/MultiTAdminServiceImpl.java | 2508 ++++---- .../alfresco/repo/usage/ContentUsageImpl.java | 12 +- .../repo/workflow/WorkflowDeployer.java | 18 +- 43 files changed, 16171 insertions(+), 16073 deletions(-) create mode 100644 source/java/org/alfresco/repo/security/authentication/AuthenticationContext.java create mode 100644 source/java/org/alfresco/repo/security/authentication/AuthenticationContextImpl.java diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 6753db6a70..bf2442e62e 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -41,8 +41,8 @@ - - + + @@ -59,8 +59,8 @@ - - + + @@ -78,8 +78,8 @@ - - + + diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 4e83d14459..cf126285e8 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -152,6 +152,12 @@ + + + + + + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 1fcdf4f55b..cb3aeae6d8 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -194,8 +194,8 @@ - - + + @@ -222,8 +222,8 @@ - - + + ${server.transaction.allow-writes} diff --git a/config/alfresco/linkvalidation-service-context.xml b/config/alfresco/linkvalidation-service-context.xml index abdcb4a5c8..81cbcbfc5f 100644 --- a/config/alfresco/linkvalidation-service-context.xml +++ b/config/alfresco/linkvalidation-service-context.xml @@ -221,8 +221,8 @@ lazy-init="true" init-method="register"> - - + + diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index 076c998d7f..c6a659cc95 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -10,7 +10,7 @@ --> - + diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index 8cfcfb0e67..8bd3f9175f 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -80,7 +80,7 @@ - + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 162329b4d6..dccc441e81 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -66,8 +66,8 @@ - - + + diff --git a/config/alfresco/preference-service-context.xml b/config/alfresco/preference-service-context.xml index bc96159453..d9a3ffddc1 100644 --- a/config/alfresco/preference-service-context.xml +++ b/config/alfresco/preference-service-context.xml @@ -42,7 +42,7 @@ - + diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index 5ec4771ebc..8d82847d14 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -65,7 +65,7 @@ - + diff --git a/config/alfresco/usage-services-context.xml b/config/alfresco/usage-services-context.xml index 5cb14358b1..8347c8ade4 100644 --- a/config/alfresco/usage-services-context.xml +++ b/config/alfresco/usage-services-context.xml @@ -24,7 +24,7 @@ - + ${system.usages.enabled} diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index b13e2cb449..7fb3c41591 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -9,7 +9,7 @@ - + diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index 073ff5dfda..7bac032105 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -1,2671 +1,2671 @@ -/* - * 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.filesys.repo; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.InetAddress; -import java.util.Date; -import java.util.List; - -import javax.transaction.UserTransaction; - -import org.alfresco.config.ConfigElement; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; -import org.alfresco.filesys.alfresco.AlfrescoNetworkFile; -import org.alfresco.filesys.state.FileState; -import org.alfresco.filesys.state.FileStateLockManager; -import org.alfresco.filesys.state.FileState.FileStateStatus; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.core.DeviceContext; -import org.alfresco.jlan.server.core.DeviceContextException; -import org.alfresco.jlan.server.filesys.AccessDeniedException; -import org.alfresco.jlan.server.filesys.AccessMode; -import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException; -import org.alfresco.jlan.server.filesys.DiskInterface; -import org.alfresco.jlan.server.filesys.FileAttribute; -import org.alfresco.jlan.server.filesys.FileInfo; -import org.alfresco.jlan.server.filesys.FileName; -import org.alfresco.jlan.server.filesys.FileOpenParams; -import org.alfresco.jlan.server.filesys.FileSharingException; -import org.alfresco.jlan.server.filesys.FileStatus; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; -import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile; -import org.alfresco.jlan.server.locking.FileLockingInterface; -import org.alfresco.jlan.server.locking.LockManager; -import org.alfresco.jlan.smb.SharingMode; -import org.alfresco.jlan.smb.server.SMBServer; -import org.alfresco.jlan.smb.server.SMBSrvSession; -import org.alfresco.jlan.util.WildCard; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.lock.NodeLockedException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.repository.ContentService; -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.StoreRef; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Content repository filesystem driver class - * - *

Provides a filesystem interface for various protocols such as SMB/CIFS and FTP. - * - * @author Derek Hulley - */ -public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface -{ - // Logging - - private static final Log logger = LogFactory.getLog(ContentDiskDriver.class); - - // Configuration key names - - private static final String KEY_STORE = "store"; - private static final String KEY_ROOT_PATH = "rootPath"; - private static final String KEY_RELATIVE_PATH = "relativePath"; - - // Token name to substitute current servers DNS name or TCP/IP address into the webapp URL - - private static final String TokenLocalName = "${localname}"; - - // Services and helpers - - private CifsHelper cifsHelper; - private NamespaceService namespaceService; - private NodeService nodeService; - private SearchService searchService; - private ContentService contentService; - private MimetypeService mimetypeService; - private PermissionService permissionService; - private FileFolderService fileFolderService; - - private AuthenticationComponent authComponent; - private AuthenticationService authService; - - // Node monitor factory - - private NodeMonitorFactory m_nodeMonitorFactory; - - // Lock manager - - private static LockManager _lockManager = new FileStateLockManager(); - - /** - * Class constructor - * - * @param serviceRegistry to connect to the repository services - */ - public ContentDiskDriver(CifsHelper cifsHelper) - { - this.cifsHelper = cifsHelper; - } - - /** - * Return the CIFS helper - * - * @return CifsHelper - */ - public final CifsHelper getCifsHelper() - { - return this.cifsHelper; - } - - /** - * Return the authentication service - * - * @return AuthenticationService - */ - public final AuthenticationService getAuthenticationService() - { - return authService; - } - - /** - * Return the authentication component - * - * @return AuthenticationComponent - */ - public final AuthenticationComponent getAuthComponent() { - return authComponent; - } - - /** - * Return the node service - * - * @return NodeService - */ - public final NodeService getNodeService() - { - return this.nodeService; - } - - /** - * Return the content service - * - * @return ContentService - */ - public final ContentService getContentService() - { - return this.contentService; - } - - /** - * Return the namespace service - * - * @return NamespaceService - */ - public final NamespaceService getNamespaceService() - { - return this.namespaceService; - } - - /** - * Return the search service - * - * @return SearchService - */ - public final SearchService getSearchService(){ - return this.searchService; - } - - /** - * Return the file folder service - * - * @return FileFolderService - */ - public final FileFolderService getFileFolderService() { - return this.fileFolderService; - } - - /** - * Return the permission service - * - * @return PermissionService - */ - public final PermissionService getPermissionService() { - return this.permissionService; - } - - /** - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @param namespaceService the namespace service - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param searchService the search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Set the permission service - * - * @param permissionService PermissionService - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Set the authentication component - * - * @param authComponent AuthenticationComponent - */ - public void setAuthenticationComponent(AuthenticationComponent authComponent) - { - this.authComponent = authComponent; - } - - /** - * Set the authentication service - * - * @param authService AuthenticationService - */ - public void setAuthenticationService(AuthenticationService authService) - { - this.authService = authService; - } - - /** - * Set the file folder service - * - * @param fileService FileFolderService - */ - public void setFileFolderService(FileFolderService fileService) - { - fileFolderService = fileService; - } - - /** - * @param mimetypeService service for helping with mimetypes and encoding - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - /** - * Set the node monitor factory - * - * @param nodeMonitorFactory NodeMonitorFactory - */ - public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) { - m_nodeMonitorFactory = nodeMonitorFactory; - } - - /** - * Parse and validate the parameter string and create a device context object for this instance - * of the shared device. The same DeviceInterface implementation may be used for multiple - * shares. - * - * @param shareName String - * @param args ConfigElement - * @return DeviceContext - * @exception DeviceContextException - */ - public DeviceContext createContext(String shareName, ConfigElement cfg) throws DeviceContextException - { - // Wrap the initialization in a transaction - - UserTransaction tx = getTransactionService().getUserTransaction(true); - - ContentContext context = null; - - try - { - // Use the system user as the authenticated context for the filesystem initialization - - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - - // Start the transaction - - if ( tx != null) - tx.begin(); - - // Get the store - - ConfigElement storeElement = cfg.getChild(KEY_STORE); - if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0) - { - throw new DeviceContextException("Device missing init value: " + KEY_STORE); - } - String storeValue = storeElement.getValue(); - StoreRef storeRef = new StoreRef(storeValue); - - // Connect to the repo and ensure that the store exists - - if (! nodeService.exists(storeRef)) - { - throw new DeviceContextException("Store not created prior to application startup: " + storeRef); - } - NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); - - // Get the root path - - ConfigElement rootPathElement = cfg.getChild(KEY_ROOT_PATH); - if (rootPathElement == null || rootPathElement.getValue() == null || rootPathElement.getValue().length() == 0) - { - throw new DeviceContextException("Device missing init value: " + KEY_ROOT_PATH); - } - String rootPath = rootPathElement.getValue(); - - // Find the root node for this device - - List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false); - - NodeRef rootNodeRef = null; - - if (nodeRefs.size() > 1) - { - throw new DeviceContextException("Multiple possible roots for device: \n" + - " root path: " + rootPath + "\n" + - " results: " + nodeRefs); - } - else if (nodeRefs.size() == 0) - { - // Nothing found - - throw new DeviceContextException("No root found for device: \n" + - " root path: " + rootPath); - } - else - { - // We found a node - - rootNodeRef = nodeRefs.get(0); - } - - // Check if a relative path has been specified - - ConfigElement relativePathElement = cfg.getChild(KEY_RELATIVE_PATH); - - if ( relativePathElement != null) - { - // Make sure the path is in CIFS format - - String relPath = relativePathElement.getValue().replace( '/', FileName.DOS_SEPERATOR); - - // Find the node and validate that the relative path is to a folder - - NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath); - if ( cifsHelper.isDirectory( relPathNode) == false) - throw new DeviceContextException("Relative path is not a folder, " + relativePathElement.getValue()); - - // Use the relative path node as the root of the filesystem - - rootNodeRef = relPathNode; - } - else { - - // Make sure the default root node is a folder - - if ( cifsHelper.isDirectory( rootNodeRef) == false) - throw new DeviceContextException("Root node is not a folder type node"); - } - - // Commit the transaction - - tx.commit(); - tx = null; - - // Create the context - - context = new ContentContext(shareName, storeValue, rootPath, rootNodeRef); - } - catch (Exception ex) - { - logger.error("Error during create context", ex); - } - finally - { - // Restore authentication context - - AuthenticationUtil.popAuthentication(); - - // If there is an active transaction then roll it back - - if ( tx != null) - { - try - { - tx.rollback(); - } - catch (Exception ex) - { - logger.warn("Failed to rollback transaction", ex); - } - } - } - - // Check if URL link files are enabled - - ConfigElement urlFileElem = cfg.getChild( "urlFile"); - if ( urlFileElem != null) - { - // Get the pseudo file name and web prefix path - - ConfigElement pseudoName = urlFileElem.getChild( "filename"); - ConfigElement webPath = urlFileElem.getChild( "webpath"); - - if ( pseudoName != null && webPath != null) - { - // Make sure the web prefix has a trailing slash - - String path = webPath.getValue(); - if ( path.endsWith("/") == false) - path = path + "/"; - - // URL file name must end with .url - - if ( pseudoName.getValue().endsWith(".url") == false) - throw new DeviceContextException("URL link file must end with .url, " + pseudoName.getValue()); - - // Check if the URL path name contains the local name token - - int pos = path.indexOf(TokenLocalName); - if (pos != -1) - { - - // Get the local server name - - String srvName = "localhost"; - - try - { - srvName = InetAddress.getLocalHost().getHostName(); - } - catch ( Exception ex) - { - } - - // Rebuild the host name substituting the token with the local server name - - StringBuilder hostStr = new StringBuilder(); - - hostStr.append( path.substring(0, pos)); - hostStr.append(srvName); - - pos += TokenLocalName.length(); - if (pos < path.length()) - hostStr.append( path.substring(pos)); - - path = hostStr.toString(); - } - - // Set the URL link file name and web path - - context.setURLFileName( pseudoName.getValue()); - context.setURLPrefix( path); - } - } - - // Check if locked files should be marked as offline - - ConfigElement offlineFiles = cfg.getChild( "offlineFiles"); - if ( offlineFiles != null) - { - // Enable marking locked files as offline - - cifsHelper.setMarkLockedFilesAsOffline( true); - - // Logging - - logger.info("Locked files will be marked as offline"); - } - - // Enable file state caching - - context.enableStateTable( true, getStateReaper()); - - // Initialize the I/O control handler - - if ( context.hasIOHandler()) - context.getIOHandler().initialize( this, context); - - // Install the node service monitor - - if ( cfg.getChild("disableNodeMonitor") == null && m_nodeMonitorFactory != null) { - - // Create the node monitor - - NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor( this, context); - context.setNodeMonitor( nodeMonitor); - } - - // Return the context for this shared filesystem - - return context; - } - - /** - * Check if pseudo file support is enabled - * - * @param context ContentContext - * @return boolean - */ - public final boolean hasPseudoFileInterface(ContentContext context) - { - return context.hasPseudoFileInterface(); - } - - /** - * Return the pseudo file support implementation - * - * @param context ContentContext - * @return PseudoFileInterface - */ - public final PseudoFileInterface getPseudoFileInterface(ContentContext context) - { - return context.getPseudoFileInterface(); - } - - /** - * Determine if the disk device is read-only. - * - * @param sess Server session - * @param ctx Device context - * @return boolean - * @exception java.io.IOException If an error occurs. - */ - public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException - { - if (cifsHelper.isReadOnly()) - { - return true; - } - else - { - return false; - } - } - - /** - * Get the file information for the specified file. - * - * @param sess Server session - * @param tree Tree connection - * @param name File name/path that information is required for. - * @return File information if valid, else null - * @exception java.io.IOException The exception description. - */ - public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException - { - // Start a transaction - - beginReadTransaction( session); - - // Get the device root - - ContentContext ctx = (ContentContext) tree.getContext(); - NodeRef infoParentNodeRef = ctx.getRootNode(); - - if ( path == null || path.length() == 0) - path = FileName.DOS_SEPERATOR_STR; - - String infoPath = path; - - try - { - // Check if the path is to a pseudo file - - FileInfo finfo = null; - - if ( hasPseudoFileInterface(ctx)) - { - // Make sure the parent folder has a file state, and the path exists - - String[] paths = FileName.splitPath( path); - FileState fstate = ctx.getStateTable().findFileState( paths[0]); - - if ( fstate == null) - { - NodeRef nodeRef = getNodeForPath(tree, paths[0]); - - if ( nodeRef != null) - { - // Get the file information for the node - - finfo = cifsHelper.getFileInformation(nodeRef); - } - - // Create the file state - - fstate = ctx.getStateTable().findFileState( paths[0], true, true); - - fstate.setFileStatus( FileStatus.DirectoryExists); - fstate.setNodeRef( nodeRef); - - // Add pseudo files to the folder - - getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added file state for pseudo files folder (getinfo) - " + paths[0]); - } - else if ( fstate.hasPseudoFiles() == false) - { - // Make sure the file state has the node ref - - if ( fstate.hasNodeRef() == false) - { - // Get the node for the folder path - - fstate.setNodeRef( getNodeForPath( tree, paths[0])); - } - - // Add pseudo files for the parent folder - - getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added pseudo files for folder (exists) - " + paths[0]); - } - - - // Get the pseudo file - - PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path); - if ( pfile != null) - { - // DEBUG - if ( logger.isDebugEnabled()) - logger.debug("getInfo using pseudo file info for " + path); - - FileInfo pseudoFileInfo = pfile.getFileInfo(); - if (cifsHelper.isReadOnly()) - { - int attr = pseudoFileInfo.getFileAttributes(); - if (( attr & FileAttribute.ReadOnly) == 0) - { - attr += FileAttribute.ReadOnly; - pseudoFileInfo.setFileAttributes(attr); - } - } - return pfile.getFileInfo(); - } - } - - // Get the node ref for the path, chances are there is a file state in the cache - - NodeRef nodeRef = getNodeForPath(tree, infoPath); - - if ( nodeRef != null) - { - // Get the file information for the node - - finfo = cifsHelper.getFileInformation(nodeRef); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("getInfo using cached noderef for path " + path); - } - - // If the required node was not in the state cache, the parent folder node might be - - - if ( finfo == null) - { - String[] paths = FileName.splitPath( path); - - if ( paths[0] != null && paths[0].length() > 1) - { - // Find the node ref for the folder being searched - - nodeRef = getNodeForPath(tree, paths[0]); - - if ( nodeRef != null) - { - infoParentNodeRef = nodeRef; - infoPath = paths[1]; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("getInfo using cached noderef for parent " + path); - } - } - - // Access the repository to get the file information - - finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath); - - // DEBUG - - if (logger.isDebugEnabled()) - { - logger.debug("Getting file information: \n" + - " path: " + path + "\n" + - " file info: " + finfo); - } - } - - // Set the file id for the file using the relative path - - if ( finfo != null) { - - // Set the file id - - finfo.setFileId( path.hashCode()); - - // Copy cached timestamps, if available - - FileState fstate = getStateForPath(tree, infoPath); - if ( fstate != null) { - if ( fstate.hasAccessDateTime()) - finfo.setAccessDateTime(fstate.getAccessDateTime()); - if ( fstate.hasChangeDateTime()) - finfo.setChangeDateTime(fstate.getChangeDateTime()); - if ( fstate.hasModifyDateTime()) - finfo.setModifyDateTime(fstate.getModifyDateTime()); - } - } - - // Return the file information - - return finfo; - } - catch (FileNotFoundException e) - { - // Debug - - if (logger.isDebugEnabled()) - logger.debug("Getting file information - File not found: \n" + - " path: " + path); - throw e; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Get file info - access denied, " + path); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Get file information " + path); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Get file info error", ex); - - // Convert to a general I/O exception - - throw new IOException("Get file information " + path); - } - } - - /** - * Start a new search on the filesystem using the specified searchPath that may contain - * wildcards. - * - * @param sess Server session - * @param tree Tree connection - * @param searchPath File(s) to search for, may include wildcards. - * @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute. - * @return SearchContext - * @exception java.io.FileNotFoundException If the search could not be started. - */ - public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException - { - try - { - // Access the device context - - ContentContext ctx = (ContentContext) tree.getContext(); - - String searchFileSpec = searchPath; - NodeRef searchRootNodeRef = ctx.getRootNode(); - FileState searchFolderState = null; - - // Create the transaction - - beginReadTransaction( sess); - - // If the state table is available see if we can speed up the search using either cached - // file information or find the folder node to be searched without having to walk the path - - String[] paths = FileName.splitPath(searchPath); - - if ( ctx.hasStateTable()) - { - // See if the folder to be searched has a file state, we can avoid having to walk the path - - if ( paths[0] != null && paths[0].length() >= 1) - { - // Find the node ref for the folder being searched - - NodeRef nodeRef = getNodeForPath(tree, paths[0]); - - // Get the file state for the folder being searched - - searchFolderState = getStateForPath(tree, paths[0]); - if ( searchFolderState == null) - { - // Create a file state for the folder - - searchFolderState = ctx.getStateTable().findFileState( paths[0], true, true); - } - - // Make sure the associated node is set - - if ( searchFolderState.hasNodeRef() == false) - { - // Set the associated node for the folder - - searchFolderState.setNodeRef( nodeRef); - } - - // Add pseudo files to the folder being searched - - if ( hasPseudoFileInterface(ctx)) - getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]); - - // Set the search node and file spec - - if ( nodeRef != null) - { - searchRootNodeRef = nodeRef; - searchFileSpec = paths[1]; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Search using cached noderef for path " + searchPath); - } - } - } - - // Convert the all files wildcard - - if ( searchFileSpec.equals( "*.*")) - searchFileSpec = "*"; - - // Perform the search - - List results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec); - - // Check if there are any pseudo files for the folder being searched, for CIFS only - - PseudoFileList pseudoList = null; - - if ( sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles()) - { - // If it is a wildcard search use all pseudo files - - if ( WildCard.containsWildcards(searchFileSpec)) - { - // Get the list of pseudo files for the search path - - pseudoList = searchFolderState.getPseudoFileList(); - - // Check if the wildcard is for all files or a subset - - if ( searchFileSpec.equals( "*") == false && pseudoList != null && pseudoList.numberOfFiles() > 0) - { - // Generate a subset of pseudo files that match the wildcard search pattern - - WildCard wildCard = new WildCard( searchFileSpec, false); - PseudoFileList filterList = null; - - for ( int i = 0; i > pseudoList.numberOfFiles(); i++) - { - PseudoFile pseudoFile = pseudoList.getFileAt( i); - if ( wildCard.matchesPattern( pseudoFile.getFileName())) - { - // Add the pseudo file to the filtered list - - if ( filterList == null) - filterList = new PseudoFileList(); - filterList.addFile( pseudoFile); - } - } - - // Use the filtered pseudo file list, or null if there were no matches - - pseudoList = filterList; - } - } - else if ( results == null || results.size() == 0) - { - // Check if the required file is in the pseudo file list - - String fname = paths[1]; - - if ( fname != null) - { - // Search for a matching pseudo file - - PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true); - if ( pfile != null) - { - // Create a file list with the required file - - pseudoList = new PseudoFileList(); - pseudoList.addFile( pfile); - } - } - } - } - - // Build the search context to store the results - - SearchContext searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0]); - - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Started search: \n" + - " search path: " + searchPath + "\n" + - " attributes: " + attributes); - } - return searchCtx; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Start search - access denied, " + searchPath); - - // Convert to a file not found status - - throw new FileNotFoundException("Start search " + searchPath); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Start search", ex); - - // Convert to a file not found status - - throw new FileNotFoundException("Start search " + searchPath); - } - } - - /** - * Check if the specified file exists, and whether it is a file or directory. - * - * @param sess Server session - * @param tree Tree connection - * @param name java.lang.String - * @return int - * @see FileStatus - */ - public int fileExists(SrvSession sess, TreeConnection tree, String name) - { - - int status = FileStatus.Unknown; - - try - { - // Check for a cached file state - - ContentContext ctx = (ContentContext) tree.getContext(); - FileState fstate = null; - - if ( ctx.hasStateTable()) - ctx.getStateTable().findFileState(name); - - if ( fstate != null) - { - FileStateStatus fsts = fstate.getFileStatus(); - - if ( fsts == FileStateStatus.FileExists) - status = FileStatus.FileExists; - else if ( fsts == FileStateStatus.FolderExists) - status = FileStatus.DirectoryExists; - else if ( fsts == FileStateStatus.NotExist || fsts == FileStateStatus.Renamed) - status = FileStatus.NotExist; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Cache hit - fileExists() " + name + ", sts=" + status); - } - else - { - // Check if pseudo files are enabled - - if ( hasPseudoFileInterface(ctx)) - { - // Check if the file name is a pseudo file name - - if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) { - - // Make sure the parent folder has a file state, and the path exists - - String[] paths = FileName.splitPath( name); - fstate = ctx.getStateTable().findFileState( paths[0]); - - if ( fstate == null) { - - // Check if the path exists - - if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists) - { - // Create the file state - - fstate = ctx.getStateTable().findFileState( paths[0], true, true); - - fstate.setFileStatus( FileStatus.DirectoryExists); - - // Get the node for the folder path - - beginReadTransaction( sess); - fstate.setNodeRef( getNodeForPath( tree, paths[0])); - - // Add pseudo files to the folder - - getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added file state for pseudo files folder (exists) - " + paths[0]); - } - } - else if ( fstate.hasPseudoFiles() == false) - { - // Make sure the file state has the node ref - - if ( fstate.hasNodeRef() == false) - { - // Create the transaction - - beginReadTransaction( sess); - - // Get the node for the folder path - - fstate.setNodeRef( getNodeForPath( tree, paths[0])); - } - - // Add pseudo files for the parent folder - - getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added pseudo files for folder (exists) - " + paths[0]); - } - - // Check if the path is to a pseudo file - - PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name); - if ( pfile != null) - { - // Indicate that the file exists - - status = FileStatus.FileExists; - } - else - { - // Failed to find pseudo file - - if ( logger.isDebugEnabled()) - logger.debug( "Failed to find pseudo file (exists) - " + name); - } - } - } - - // If the file is not a pseudo file then search for the file - - if ( status == FileStatus.Unknown) - { - // Create the transaction - - beginReadTransaction( sess); - - // Get the file information to check if the file/folder exists - - FileInfo info = getFileInformation(sess, tree, name); - if (info.isDirectory()) - { - status = FileStatus.DirectoryExists; - } - else - { - status = FileStatus.FileExists; - } - } - } - } - catch (FileNotFoundException e) - { - status = FileStatus.NotExist; - } - catch (IOException e) - { - // Debug - - logger.debug("File exists error, " + name, e); - - status = FileStatus.NotExist; - } - - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("File status determined: \n" + - " name: " + name + "\n" + - " status: " + status); - } - - // Return the file/folder status - - return status; - } - - /** - * Open a file or folder - * - * @param sess SrvSession - * @param tree TreeConnection - * @param params FileOpenParams - * @return NetworkFile - * @exception IOException - */ - public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException - { - // Create the transaction - - beginReadTransaction( sess); - - try - { - // Get the node for the path - - ContentContext ctx = (ContentContext) tree.getContext(); - - // Check if pseudo files are enabled - - if ( hasPseudoFileInterface(ctx)) - { - // Check if the file name is a pseudo file name - - String path = params.getPath(); - - if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, path)) { - - // Make sure the parent folder has a file state, and the path exists - - String[] paths = FileName.splitPath( path); - FileState fstate = ctx.getStateTable().findFileState( paths[0]); - - if ( fstate == null) { - - // Check if the path exists - - if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists) - { - // Create the file state and add any pseudo files - - fstate = ctx.getStateTable().findFileState( paths[0], true, true); - - fstate.setFileStatus( FileStatus.DirectoryExists); - getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added file state for pseudo files folder (open) - " + paths[0]); - } - } - else if ( fstate.hasPseudoFiles() == false) - { - // Add pseudo files for the parent folder - - getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "Added pseudo files for folder (open) - " + paths[0]); - } - - // Check if the path is to a pseudo file - - PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath()); - if ( pfile != null) - { - // Create a network file to access the pseudo file data - - return pfile.getFile( params.getPath()); - } - else - { - // Failed to find pseudo file - - if ( logger.isDebugEnabled()) - logger.debug( "Failed to find pseudo file (open) - " + params.getPath()); - } - } - } - - // Not a pseudo file, try and open a normal file/folder node - - NodeRef nodeRef = getNodeForPath(tree, params.getPath()); - - // Check permissions on the file/folder node - // - // Check for read access - - if ( params.hasAccessMode(AccessMode.NTRead) && - permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) - throw new AccessDeniedException("No read access to " + params.getFullPath()); - - // Check for write access - - if ( params.hasAccessMode(AccessMode.NTWrite) && - permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) - throw new AccessDeniedException("No write access to " + params.getFullPath()); - - // Check for delete access - -// if ( params.hasAccessMode(AccessMode.NTDelete) && -// permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) -// throw new AccessDeniedException("No delete access to " + params.getFullPath()); - - // Check if the file has a lock - - String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE); - - if ( params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null) - throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath()); - - // Check if there is a file state for the file - - FileState fstate = null; - - if ( ctx.hasStateTable()) - { - // Check if there is a file state for the file - - fstate = ctx.getStateTable().findFileState( params.getPath()); - - if ( fstate != null) - { - // Check if the file exists - - if ( fstate.exists() == false) - throw new FileNotFoundException(); - - // Check if the open request shared access indicates exclusive file access - - if ( fstate != null && params.getSharedAccess() == SharingMode.NOSHARING && - fstate.getOpenCount() > 0) - throw new FileSharingException("File already open, " + params.getPath()); - } - } - - // Check if the node is a link node - - NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); - AlfrescoNetworkFile netFile = null; - - if ( linkRef == null) - { - // Create the network file - - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params); - } - else - { - // Get the CIFS server name - - String srvName = null; - SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer( "CIFS"); - - if ( cifsServer != null) - { - // Use the CIFS server name in the URL - - srvName = cifsServer.getServerName(); - } - else - { - // Use the local server name in the URL - - srvName = InetAddress.getLocalHost().getHostName(); - } - - // Convert the target node to a path, convert to URL format - - String path = getPathForNode( tree, linkRef); - path = path.replace( FileName.DOS_SEPERATOR, '/'); - - // Build the URL file data - - StringBuilder urlStr = new StringBuilder(); - - urlStr.append("[InternetShortcut]\r\n"); - urlStr.append("URL=file://"); - urlStr.append( srvName); - urlStr.append("/"); - urlStr.append( tree.getSharedDevice().getName()); - urlStr.append( path); - urlStr.append("\r\n"); - - // Create the in memory pseudo file for the URL link - - byte[] urlData = urlStr.toString().getBytes(); - - // Get the file information for the link node - - FileInfo fInfo = cifsHelper.getFileInformation( nodeRef); - - // Set the file size to the actual data length - - fInfo.setFileSize( urlData.length); - - // Create the network file using the in-memory file data - - netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef); - netFile.setFullName( params.getPath()); - } - - // Generate a file id for the file - - if ( netFile != null) - netFile.setFileId( params.getPath().hashCode()); - - // Create a file state for the open file - - if ( ctx.hasStateTable()) - { - if ( fstate == null) - fstate = ctx.getStateTable().findFileState(params.getPath(), params.isDirectory(), true); - - // Update the file state, cache the node - - fstate.incrementOpenCount(); - fstate.setNodeRef(nodeRef); - - // Store the state with the file - - netFile.setFileState( fstate); - } - - // If the file has been opened for overwrite then truncate the file to zero length, this will - // also prevent the existing content data from being copied to the new version of the file - - if ( params.isOverwrite() && netFile != null) - { - // Truncate the file to zero length - - netFile.truncateFile( 0L); - } - - // Debug - - if (logger.isDebugEnabled()) - { - logger.debug("Opened network file: \n" + - " path: " + params.getPath() + "\n" + - " file open parameters: " + params + "\n" + - " network file: " + netFile); - } - - // Return the network file - - return netFile; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Open file - access denied, " + params.getFullPath()); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Open file " + params.getFullPath()); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Open file error", ex); - - // Convert to a general I/O exception - - throw new IOException("Open file " + params.getFullPath()); - } - } - - /** - * Create a new file on the file system. - * - * @param sess Server session - * @param tree Tree connection - * @param params File create parameters - * @return NetworkFile - * @exception java.io.IOException If an error occurs. - */ - public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - try - { - // Get the device root - - ContentContext ctx = (ContentContext) tree.getContext(); - NodeRef deviceRootNodeRef = ctx.getRootNode(); - - String path = params.getPath(); - - // If the state table is available then try to find the parent folder node for the new file - // to save having to walk the path - - if ( ctx.hasStateTable()) - { - // See if the parent folder has a file state, we can avoid having to walk the path - - String[] paths = FileName.splitPath(path); - if ( paths[0] != null && paths[0].length() > 1) - { - // Find the node ref for the folder being searched - - NodeRef nodeRef = getNodeForPath(tree, paths[0]); - - if ( nodeRef != null) - { - deviceRootNodeRef = nodeRef; - path = paths[1]; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Create file using cached noderef for path " + paths[0]); - } - } - } - - // Create it - the path will be created, if necessary - - NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, true); - - // Create the network file - - ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params); - - // Always allow write access to a newly created file - - netFile.setGrantedAccess(NetworkFile.READWRITE); - - // Truncate the file so that the content stream is created - - netFile.truncateFile( 0L); - - // Generate a file id for the file - - if ( netFile != null) - netFile.setFileId( params.getPath().hashCode()); - - // Add a file state for the new file/folder - - if ( ctx.hasStateTable()) - { - FileState fstate = ctx.getStateTable().findFileState(params.getPath(), false, true); - if ( fstate != null) - { - // Indicate that the file is open - - fstate.setFileStatus(FileStateStatus.FileExists); - fstate.incrementOpenCount(); - fstate.setNodeRef(nodeRef); - - // Store the file state with the file - - netFile.setFileState( fstate); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Create file, state=" + fstate); - } - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Created file: \n" + - " path: " + path + "\n" + - " file open parameters: " + params + "\n" + - " node: " + nodeRef + "\n" + - " network file: " + netFile); - } - - return netFile; - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Create file - access denied, " + params.getFullPath()); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Create file " + params.getFullPath()); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Create file error", ex); - - // Convert to a general I/O exception - - throw new IOException("Create file " + params.getFullPath()); - } - - } - - /** - * Create a new directory on this file system. - * - * @param sess Server session - * @param tree Tree connection. - * @param params Directory create parameters - * @exception java.io.IOException If an error occurs. - */ - public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - try - { - // get the device root - - ContentContext ctx = (ContentContext) tree.getContext(); - NodeRef deviceRootNodeRef = ctx.getRootNode(); - - String path = params.getPath(); - - // If the state table is available then try to find the parent folder node for the new folder - // to save having to walk the path - - if ( ctx.hasStateTable()) - { - // See if the parent folder has a file state, we can avoid having to walk the path - - String[] paths = FileName.splitPath(path); - if ( paths[0] != null && paths[0].length() > 1) - { - // Find the node ref for the folder being searched - - NodeRef nodeRef = getNodeForPath(tree, paths[0]); - - if ( nodeRef != null) - { - deviceRootNodeRef = nodeRef; - path = paths[1]; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Create file using cached noderef for path " + paths[0]); - } - } - } - - // Create it - the path will be created, if necessary - - NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, false); - - // Add a file state for the new folder - - if ( ctx.hasStateTable()) - { - FileState fstate = ctx.getStateTable().findFileState( params.getPath(), true, true); - if ( fstate != null) - { - // Indicate that the file is open - - fstate.setFileStatus(FileStateStatus.FolderExists); - fstate.incrementOpenCount(); - fstate.setNodeRef(nodeRef); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Creaste folder, state=" + fstate); - } - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Created directory: \n" + - " path: " + path + "\n" + - " file open params: " + params + "\n" + - " node: " + nodeRef); - } - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Create directory - access denied, " + params.getFullPath()); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Create directory " + params.getFullPath()); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Create directory error", ex); - - // Convert to a general I/O exception - - throw new IOException("Create directory " + params.getFullPath()); - } - } - - /** - * Delete the directory from the filesystem. - * - * @param sess Server session - * @param tree Tree connection - * @param dir Directory name. - * @exception java.io.IOException The exception description. - */ - public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - // get the device root - - ContentContext ctx = (ContentContext) tree.getContext(); - NodeRef deviceRootNodeRef = ctx.getRootNode(); - - try - { - // Get the node for the folder - - NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir); - if (fileFolderService.exists(nodeRef)) - { - // Check if the folder is empty - - if ( cifsHelper.isFolderEmpty( nodeRef) == true) { - - // Delete the folder node - - fileFolderService.delete(nodeRef); - - // Remove the file state - - if ( ctx.hasStateTable()) - ctx.getStateTable().removeFileState(dir); - } - else - throw new DirectoryNotEmptyException( dir); - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted directory: \n" + - " directory: " + dir + "\n" + - " node: " + nodeRef); - } - } - catch (FileNotFoundException e) - { - // already gone - if (logger.isDebugEnabled()) - { - logger.debug("Deleted directory : \n" + - " directory: " + dir); - } - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete directory - access denied, " + dir); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Delete directory " + dir); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete directory", ex); - - // Convert to a general I/O exception - - throw new IOException("Delete directory " + dir); - } - } - - /** - * Flush any buffered output for the specified file. - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file context. - * @exception java.io.IOException The exception description. - */ - public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException - { - // Flush the file data - - file.flushFile(); - } - - /** - * Close the file. - * - * @param sess Server session - * @param tree Tree connection. - * @param param Network file context. - * @exception java.io.IOException If an error occurs. - */ - public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - // Get the associated file state - - ContentContext ctx = (ContentContext) tree.getContext(); - - if ( ctx.hasStateTable()) - { - FileState fstate = ctx.getStateTable().findFileState(file.getFullName()); - if ( fstate != null) - fstate.decrementOpenCount(); - } - - // Defer to the network file to close the stream and remove the content - - file.closeFile(); - - // Remove the node if marked for delete - - if (file.hasDeleteOnClose()) - { - // Check if the file is a noderef based file - - if ( file instanceof NodeRefNetworkFile) - { - NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; - NodeRef nodeRef = nodeNetFile.getNodeRef(); - - // We don't know how long the network file has had the reference, so check for existence - - if (fileFolderService.exists(nodeRef)) - { - try - { - // Delete the file - - fileFolderService.delete(nodeRef); - - // Remove the file state - - if ( ctx.hasStateTable()) - ctx.getStateTable().removeFileState(file.getFullName()); - - // Commit the current transaction - -// sess.endTransaction(); -// beginReadTransaction( sess); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete on close - access denied, " + file.getFullName()); - - // Convert to a filesystem access denied exception - - throw new AccessDeniedException("Delete on close " + file.getFullName()); - } - } - } - else if ( file instanceof PseudoNetworkFile || - file instanceof MemoryNetworkFile) - { - // Delete the pseudo file - - if ( hasPseudoFileInterface(ctx)) - { - // Delete the pseudo file - - getPseudoFileInterface(ctx).deletePseudoFile( sess, tree, file.getFullName()); - } - } - } - - // DEBUG - - if (logger.isDebugEnabled()) - { - logger.debug("Closed file: \n" + - " network file: " + file + "\n" + - " deleted on close: " + file.hasDeleteOnClose()); - } - } - - /** - * Delete the specified file. - * - * @param sess Server session - * @param tree Tree connection - * @param file NetworkFile - * @exception java.io.IOException The exception description. - */ - public void deleteFile(SrvSession sess, TreeConnection tree, String name) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - // Get the device context - - ContentContext ctx = (ContentContext) tree.getContext(); - - try - { - // Get the node - - NodeRef nodeRef = getNodeForPath(tree, name); - if (fileFolderService.exists(nodeRef)) - { - fileFolderService.delete(nodeRef); - - // Remove the file state - - if ( ctx.hasStateTable()) - ctx.getStateTable().removeFileState(name); - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted file: \n" + - " file: " + name + "\n" + - " node: " + nodeRef); - } - } - catch (NodeLockedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete file - access denied (locked)"); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Delete " + name); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete file - access denied"); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Delete " + name); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Delete file error", ex); - - // Convert to a general I/O exception - - throw new IOException("Delete file " + name); - } - } - - /** - * Rename the specified file. - * - * @param sess Server session - * @param tree Tree connection - * @param oldName java.lang.String - * @param newName java.lang.String - * @exception java.io.IOException The exception description. - */ - public void renameFile(SrvSession sess, TreeConnection tree, String oldName, String newName) throws IOException - { - // Create the transaction - - beginWriteTransaction( sess); - - try - { - // Get the device context - - ContentContext ctx = (ContentContext) tree.getContext(); - - // Get the file/folder to move - - NodeRef nodeToMoveRef = getNodeForPath(tree, oldName); - - // Check if the node is a link node - - if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) - throw new AccessDeniedException("Cannot rename link nodes"); - - // Get the new target folder - it must be a folder - - String[] splitPaths = FileName.splitPath(newName); - NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]); - String name = splitPaths[1]; - - // Check if this is a rename within the same folder - - String[] oldPaths = FileName.splitPath( oldName); - - boolean sameFolder = false; - if ( splitPaths[0].equalsIgnoreCase( oldPaths[0])) - sameFolder = true; - - // Update the state table - - boolean relinked = false; - if ( ctx.hasStateTable()) - { - // Check if the file rename can be relinked to a previous version - - if ( !cifsHelper.isDirectory(nodeToMoveRef) ) - { - // Check if there is a renamed file state for the new file name - - FileState renState = ctx.getStateTable().removeFileState(newName); - - if ( renState != null && renState.getFileStatus() == FileStateStatus.Renamed) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug(" Found rename state, relinking, " + renState); - - // Relink the new version of the file data to the previously renamed node so that it - // picks up version history and other settings. - - cifsHelper.relinkNode( renState.getNodeRef(), nodeToMoveRef, targetFolderRef, name); - relinked = true; - - // Link the node ref for the associated rename state - - if ( renState.hasRenameState()) - renState.getRenameState().setNodeRef(nodeToMoveRef); - - // Remove the file state for the old file name - - ctx.getStateTable().removeFileState(oldName); - - // Get, or create, a file state for the new file path - - FileState fstate = ctx.getStateTable().findFileState(newName, false, true); - - fstate.setNodeRef(renState.getNodeRef()); - fstate.setFileStatus(FileStateStatus.FileExists); - } - } - else - { - // Get the file state for the folder, if available - - FileState fstate = ctx.getStateTable().findFileState(oldName); - - if ( fstate != null) - { - // Update the file state index to use the new name - - ctx.getStateTable().renameFileState(newName, fstate); - } - } - } - - // Move the file/folder, if not relinked to previous version history - - if (!relinked) - { - // Move or rename the file/folder - - if ( sameFolder == true) - { - // Check if the new file name is a temporary file name - - String newNameNorm = newName.toLowerCase(); - boolean isTempFile = false; - - if ( newNameNorm.endsWith(".tmp") || newNameNorm.endsWith(".temp")) { - - // Add the temporary aspect, also prevents versioning - - nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_TEMPORARY, null); - - // Indicate that the new file is a temporary file - - isTempFile = true; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Added Temporary aspect to renamed file " + newName); - } - - // Rename the file/folder - - cifsHelper.rename(nodeToMoveRef, name); - - // Check if the temporary aspect should be removed from the renamed file - - String oldNameNorm = oldName.toLowerCase(); - - if ( isTempFile == false && (oldNameNorm.endsWith(".tmp") || oldNameNorm.endsWith(".temp"))) { - - // Remove the temporary aspect - - nodeService.removeAspect(nodeToMoveRef, ContentModel.ASPECT_TEMPORARY); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Removed Temporary aspect from renamed file " + newName); - } - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Renamed file: from: " + oldName + " to: " + newName); - } - else - { - // Move the file/folder - - cifsHelper.move(nodeToMoveRef, targetFolderRef, name); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Moved file: from: " + oldName + " to: " + newName); - } - - // Check if we renamed a file, if so then cache the rename details for a short period - // in case another file renamed to the old name. MS Word uses renames to move a new - // version of a document into place so we need to reconnect the version history. - - if ( !cifsHelper.isDirectory(nodeToMoveRef)) - { - // Get or create a new file state for the old file path - - FileState fstate = ctx.getStateTable().findFileState(oldName, false, true); - - // Make sure the file state is cached for a short while, the file may not be open so the - // file state could be expired - - fstate.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout); - - // Indicate that this is a renamed file state, set the node ref of the file that was renamed - - fstate.setFileStatus(FileStateStatus.Renamed); - fstate.setNodeRef(nodeToMoveRef); - - // Get, or create, a file state for the new file path - - FileState newState = ctx.getStateTable().findFileState(newName, false, true); - - newState.setNodeRef(nodeToMoveRef); - newState.setFileStatus(FileStateStatus.FileExists); - - // Link the renamed state to the new state - - fstate.setRenameState(newState); - - // DEBUG - - if ( logger.isDebugEnabled()) - { - logger.debug("Cached rename state for " + oldName + ", state=" + fstate); - logger.debug(" new name " + newName + ", state=" + newState); - } - } - } - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Moved node: from: " + oldName + " to: " + newName); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Rename file - access denied, " + oldName); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Rename file " + oldName); - } - catch (NodeLockedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Rename file", ex); - - // Convert to an filesystem access denied exception - - throw new AccessDeniedException("Node locked " + oldName); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Rename file", ex); - - // Convert to a general I/O exception - - throw new AccessDeniedException("Rename file " + oldName); - } - } - - /** - * Set file information - * - * @param sess SrvSession - * @param tree TreeConnection - * @param name String - * @param info FileInfo - * @exception IOException - */ - public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) throws IOException - { - try - { - // Get the device context - - ContentContext ctx = (ContentContext) tree.getContext(); - - // Check if pseudo files are enabled - - if ( hasPseudoFileInterface(ctx) && - getPseudoFileInterface(ctx).isPseudoFile( sess, tree, name)) - { - // Allow the file information to be changed - - return; - } - - // Get the file/folder node - - NodeRef nodeRef = getNodeForPath(tree, name); - FileState fstate = getStateForPath(tree, name); - - // Check permissions on the file/folder node - - if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) - throw new AccessDeniedException("No write access to " + name); - - if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) - throw new AccessDeniedException("No delete access to " + name); - - // Check if the file is being marked for deletion, if so then check if the file is locked - - if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) - { - // Start a transaction - - beginReadTransaction( sess); - - // Check if the node is locked - - if ( nodeService.hasAspect( nodeRef, ContentModel.ASPECT_LOCKABLE)) - { - // Get the lock type, if any - - String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE); - - if ( lockTypeStr != null) - throw new AccessDeniedException("Node locked, cannot mark for delete"); - } - - // Update the change date/time - - if ( fstate != null) - fstate.updateChangeDateTime(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Set deleteOnClose=true file=" + name); - } - - // Set the creation date/time - - if ( info.hasSetFlag(FileInfo.SetCreationDate)) { - - // Create the transaction - - beginWriteTransaction( sess); - - // Set the creation date on the file/folder node - - Date createDate = new Date( info.getCreationDateTime()); - nodeService.setProperty( nodeRef, ContentModel.PROP_CREATED, createDate); - - // Update the change date/time - - if ( fstate != null) - fstate.updateChangeDateTime(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Set creationDate=" + createDate + " file=" + name); - } - - // Set the modification date/time - - if ( info.hasSetFlag(FileInfo.SetModifyDate)) { - - // Create the transaction - - beginWriteTransaction( sess); - - // Set the creation date on the file/folder node - - Date modifyDate = new Date( info.getModifyDateTime()); - nodeService.setProperty( nodeRef, ContentModel.PROP_MODIFIED, modifyDate); - - // Update the change date/time - - if ( fstate != null) - fstate.updateChangeDateTime(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Set modifyDate=" + modifyDate + " file=" + name); - } - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Set file information - access denied, " + name); - - // Convert to a filesystem access denied status - - throw new AccessDeniedException("Set file information " + name); - } - catch (AlfrescoRuntimeException ex) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Open file error", ex); - - // Convert to a general I/O exception - - throw new IOException("Set file information " + name); - } - } - - /** - * Truncate a file to the specified size - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file details - * @param size New file length - * @exception java.io.IOException The exception description. - */ - public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException - { - // Truncate or extend the file to the required size - - file.truncateFile(size); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Truncated file: \n" + - " network file: " + file + "\n" + - " size: " + size); - } - } - - /** - * Read a block of data from the specified file. - * - * @param sess Session details - * @param tree Tree connection - * @param file Network file - * @param buf Buffer to return data to - * @param bufPos Starting position in the return buffer - * @param siz Maximum size of data to return - * @param filePos File offset to read data - * @return Number of bytes read - * @exception java.io.IOException The exception description. - */ - public int readFile( - SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException - { - // Check if the file is a directory - - if(file.isDirectory()) - throw new AccessDeniedException(); - - // If the content channel is not open for the file then start a transaction - - if ( file instanceof ContentNetworkFile) - { - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); - } - - // Read a block of data from the file - - int count = file.readFile(buffer, size, bufferPosition, fileOffset); - - if ( count == -1) - { - // Read count of -1 indicates a read past the end of file - - count = 0; - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Read bytes from file: \n" + - " network file: " + file + "\n" + - " buffer size: " + buffer.length + "\n" + - " buffer pos: " + bufferPosition + "\n" + - " size: " + size + "\n" + - " file offset: " + fileOffset + "\n" + - " bytes read: " + count); - } - return count; - } - - /** - * Seek to the specified file position. - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file. - * @param pos Position to seek to. - * @param typ Seek type. - * @return New file position, relative to the start of file. - */ - public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException - { - // Check if the file is a directory - - if ( file.isDirectory()) - throw new AccessDeniedException(); - - // If the content channel is not open for the file then start a transaction - - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); - - // Set the file position - - return file.seekFile(pos, typ); - } - - /** - * Write a block of data to the file. - * - * @param sess Server session - * @param tree Tree connection - * @param file Network file details - * @param buf byte[] Data to be written - * @param bufoff Offset within the buffer that the data starts - * @param siz int Data length - * @param fileoff Position within the file that the data is to be written. - * @return Number of bytes actually written - * @exception java.io.IOException The exception description. - */ - public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, - byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException - { - // If the content channel is not open for the file then start a transaction - - if ( file instanceof ContentNetworkFile) - { - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); - } - - // Write to the file - - file.writeFile(buffer, size, bufferOffset, fileOffset); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Wrote bytes to file: \n" + - " network file: " + file + "\n" + - " buffer size: " + buffer.length + "\n" + - " size: " + size + "\n" + - " file offset: " + fileOffset); - } - return size; - } - - /** - * Get the node for the specified path - * - * @param tree TreeConnection - * @param path String - * @return NodeRef - * @exception FileNotFoundException - */ - public NodeRef getNodeForPath(TreeConnection tree, String path) - throws FileNotFoundException - { - // Check if there is a cached state for the path - - ContentContext ctx = (ContentContext) tree.getContext(); - - if ( ctx.hasStateTable()) - { - // Try and get the node ref from an in memory file state - - FileState fstate = ctx.getStateTable().findFileState(path); - if ( fstate != null && fstate.hasNodeRef() && fstate.exists() ) - { - // Check that the node exists - - if (fileFolderService.exists(fstate.getNodeRef())) - { - // Bump the file states expiry time - - fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout); - - // Return the cached noderef - - return fstate.getNodeRef(); - } - else - { - ctx.getStateTable().removeFileState(path); - } - } - } - - // Search the repository for the node - - return cifsHelper.getNodeRef(ctx.getRootNode(), path); - } - - /** - * Convert a node into a share relative path - * - * @param tree TreeConnection - * @param nodeRef NodeRef - * @return String - * @exception FileNotFoundException - */ - public String getPathForNode( TreeConnection tree, NodeRef nodeRef) - throws FileNotFoundException - { - // Convert the target node to a path - - ContentContext ctx = (ContentContext) tree.getContext(); - List linkPaths = null; - - try { - linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); - } - catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) - { - throw new FileNotFoundException(); - } - - // Build the share relative path to the node - - StringBuilder pathStr = new StringBuilder(); - - for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) { - pathStr.append( FileName.DOS_SEPERATOR); - pathStr.append( fInfo.getName()); - } - - // Return the share relative path - - return pathStr.toString(); - } - - /** - * Get the file state for the specified path - * - * @param tree TreeConnection - * @param path String - * @return FileState - * @exception FileNotFoundException - */ - public FileState getStateForPath(TreeConnection tree, String path) - throws FileNotFoundException - { - // Check if there is a cached state for the path - - ContentContext ctx = (ContentContext) tree.getContext(); - FileState fstate = null; - - if ( ctx.hasStateTable()) - { - // Get the file state for a file/folder - - fstate = ctx.getStateTable().findFileState(path); - } - - // Return the file state - - return fstate; - } - - /** - * Connection opened to this disk device - * - * @param sess Server session - * @param tree Tree connection - */ - public void treeClosed(SrvSession sess, TreeConnection tree) - { - // Nothing to do - } - - /** - * Connection closed to this device - * - * @param sess Server session - * @param tree Tree connection - */ - public void treeOpened(SrvSession sess, TreeConnection tree) - { - // Nothing to do - } - - /** - * Return the lock manager used by this filesystem - * - * @param sess SrvSession - * @param tree TreeConnection - * @return LockManager - */ - public LockManager getLockManager(SrvSession sess, TreeConnection tree) { - return _lockManager; - } -} +/* + * 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.filesys.repo; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Date; +import java.util.List; + +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigElement; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; +import org.alfresco.filesys.alfresco.AlfrescoNetworkFile; +import org.alfresco.filesys.state.FileState; +import org.alfresco.filesys.state.FileStateLockManager; +import org.alfresco.filesys.state.FileState.FileStateStatus; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.AccessDeniedException; +import org.alfresco.jlan.server.filesys.AccessMode; +import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException; +import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.FileSharingException; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile; +import org.alfresco.jlan.server.locking.FileLockingInterface; +import org.alfresco.jlan.server.locking.LockManager; +import org.alfresco.jlan.smb.SharingMode; +import org.alfresco.jlan.smb.server.SMBServer; +import org.alfresco.jlan.smb.server.SMBSrvSession; +import org.alfresco.jlan.util.WildCard; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ContentService; +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.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content repository filesystem driver class + * + *

Provides a filesystem interface for various protocols such as SMB/CIFS and FTP. + * + * @author Derek Hulley + */ +public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface +{ + // Logging + + private static final Log logger = LogFactory.getLog(ContentDiskDriver.class); + + // Configuration key names + + private static final String KEY_STORE = "store"; + private static final String KEY_ROOT_PATH = "rootPath"; + private static final String KEY_RELATIVE_PATH = "relativePath"; + + // Token name to substitute current servers DNS name or TCP/IP address into the webapp URL + + private static final String TokenLocalName = "${localname}"; + + // Services and helpers + + private CifsHelper cifsHelper; + private NamespaceService namespaceService; + private NodeService nodeService; + private SearchService searchService; + private ContentService contentService; + private MimetypeService mimetypeService; + private PermissionService permissionService; + private FileFolderService fileFolderService; + + private AuthenticationContext authenticationContext; + private AuthenticationService authService; + + // Node monitor factory + + private NodeMonitorFactory m_nodeMonitorFactory; + + // Lock manager + + private static LockManager _lockManager = new FileStateLockManager(); + + /** + * Class constructor + * + * @param serviceRegistry to connect to the repository services + */ + public ContentDiskDriver(CifsHelper cifsHelper) + { + this.cifsHelper = cifsHelper; + } + + /** + * Return the CIFS helper + * + * @return CifsHelper + */ + public final CifsHelper getCifsHelper() + { + return this.cifsHelper; + } + + /** + * Return the authentication service + * + * @return AuthenticationService + */ + public final AuthenticationService getAuthenticationService() + { + return authService; + } + + /** + * Return the authentication component + * + * @return AuthenticationContext + */ + public final AuthenticationContext getAuthenticationContext() { + return authenticationContext; + } + + /** + * Return the node service + * + * @return NodeService + */ + public final NodeService getNodeService() + { + return this.nodeService; + } + + /** + * Return the content service + * + * @return ContentService + */ + public final ContentService getContentService() + { + return this.contentService; + } + + /** + * Return the namespace service + * + * @return NamespaceService + */ + public final NamespaceService getNamespaceService() + { + return this.namespaceService; + } + + /** + * Return the search service + * + * @return SearchService + */ + public final SearchService getSearchService(){ + return this.searchService; + } + + /** + * Return the file folder service + * + * @return FileFolderService + */ + public final FileFolderService getFileFolderService() { + return this.fileFolderService; + } + + /** + * Return the permission service + * + * @return PermissionService + */ + public final PermissionService getPermissionService() { + return this.permissionService; + } + + /** + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Set the permission service + * + * @param permissionService PermissionService + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Set the authentication component + * + * @param authComponent AuthenticationContext + */ + public void setAuthenticationContext(AuthenticationContext authComponent) + { + this.authenticationContext = authComponent; + } + + /** + * Set the authentication service + * + * @param authService AuthenticationService + */ + public void setAuthenticationService(AuthenticationService authService) + { + this.authService = authService; + } + + /** + * Set the file folder service + * + * @param fileService FileFolderService + */ + public void setFileFolderService(FileFolderService fileService) + { + fileFolderService = fileService; + } + + /** + * @param mimetypeService service for helping with mimetypes and encoding + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Set the node monitor factory + * + * @param nodeMonitorFactory NodeMonitorFactory + */ + public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) { + m_nodeMonitorFactory = nodeMonitorFactory; + } + + /** + * Parse and validate the parameter string and create a device context object for this instance + * of the shared device. The same DeviceInterface implementation may be used for multiple + * shares. + * + * @param shareName String + * @param args ConfigElement + * @return DeviceContext + * @exception DeviceContextException + */ + public DeviceContext createContext(String shareName, ConfigElement cfg) throws DeviceContextException + { + // Wrap the initialization in a transaction + + UserTransaction tx = getTransactionService().getUserTransaction(true); + + ContentContext context = null; + + try + { + // Use the system user as the authenticated context for the filesystem initialization + + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + // Start the transaction + + if ( tx != null) + tx.begin(); + + // Get the store + + ConfigElement storeElement = cfg.getChild(KEY_STORE); + if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0) + { + throw new DeviceContextException("Device missing init value: " + KEY_STORE); + } + String storeValue = storeElement.getValue(); + StoreRef storeRef = new StoreRef(storeValue); + + // Connect to the repo and ensure that the store exists + + if (! nodeService.exists(storeRef)) + { + throw new DeviceContextException("Store not created prior to application startup: " + storeRef); + } + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + // Get the root path + + ConfigElement rootPathElement = cfg.getChild(KEY_ROOT_PATH); + if (rootPathElement == null || rootPathElement.getValue() == null || rootPathElement.getValue().length() == 0) + { + throw new DeviceContextException("Device missing init value: " + KEY_ROOT_PATH); + } + String rootPath = rootPathElement.getValue(); + + // Find the root node for this device + + List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false); + + NodeRef rootNodeRef = null; + + if (nodeRefs.size() > 1) + { + throw new DeviceContextException("Multiple possible roots for device: \n" + + " root path: " + rootPath + "\n" + + " results: " + nodeRefs); + } + else if (nodeRefs.size() == 0) + { + // Nothing found + + throw new DeviceContextException("No root found for device: \n" + + " root path: " + rootPath); + } + else + { + // We found a node + + rootNodeRef = nodeRefs.get(0); + } + + // Check if a relative path has been specified + + ConfigElement relativePathElement = cfg.getChild(KEY_RELATIVE_PATH); + + if ( relativePathElement != null) + { + // Make sure the path is in CIFS format + + String relPath = relativePathElement.getValue().replace( '/', FileName.DOS_SEPERATOR); + + // Find the node and validate that the relative path is to a folder + + NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath); + if ( cifsHelper.isDirectory( relPathNode) == false) + throw new DeviceContextException("Relative path is not a folder, " + relativePathElement.getValue()); + + // Use the relative path node as the root of the filesystem + + rootNodeRef = relPathNode; + } + else { + + // Make sure the default root node is a folder + + if ( cifsHelper.isDirectory( rootNodeRef) == false) + throw new DeviceContextException("Root node is not a folder type node"); + } + + // Commit the transaction + + tx.commit(); + tx = null; + + // Create the context + + context = new ContentContext(shareName, storeValue, rootPath, rootNodeRef); + } + catch (Exception ex) + { + logger.error("Error during create context", ex); + } + finally + { + // Restore authentication context + + AuthenticationUtil.popAuthentication(); + + // If there is an active transaction then roll it back + + if ( tx != null) + { + try + { + tx.rollback(); + } + catch (Exception ex) + { + logger.warn("Failed to rollback transaction", ex); + } + } + } + + // Check if URL link files are enabled + + ConfigElement urlFileElem = cfg.getChild( "urlFile"); + if ( urlFileElem != null) + { + // Get the pseudo file name and web prefix path + + ConfigElement pseudoName = urlFileElem.getChild( "filename"); + ConfigElement webPath = urlFileElem.getChild( "webpath"); + + if ( pseudoName != null && webPath != null) + { + // Make sure the web prefix has a trailing slash + + String path = webPath.getValue(); + if ( path.endsWith("/") == false) + path = path + "/"; + + // URL file name must end with .url + + if ( pseudoName.getValue().endsWith(".url") == false) + throw new DeviceContextException("URL link file must end with .url, " + pseudoName.getValue()); + + // Check if the URL path name contains the local name token + + int pos = path.indexOf(TokenLocalName); + if (pos != -1) + { + + // Get the local server name + + String srvName = "localhost"; + + try + { + srvName = InetAddress.getLocalHost().getHostName(); + } + catch ( Exception ex) + { + } + + // Rebuild the host name substituting the token with the local server name + + StringBuilder hostStr = new StringBuilder(); + + hostStr.append( path.substring(0, pos)); + hostStr.append(srvName); + + pos += TokenLocalName.length(); + if (pos < path.length()) + hostStr.append( path.substring(pos)); + + path = hostStr.toString(); + } + + // Set the URL link file name and web path + + context.setURLFileName( pseudoName.getValue()); + context.setURLPrefix( path); + } + } + + // Check if locked files should be marked as offline + + ConfigElement offlineFiles = cfg.getChild( "offlineFiles"); + if ( offlineFiles != null) + { + // Enable marking locked files as offline + + cifsHelper.setMarkLockedFilesAsOffline( true); + + // Logging + + logger.info("Locked files will be marked as offline"); + } + + // Enable file state caching + + context.enableStateTable( true, getStateReaper()); + + // Initialize the I/O control handler + + if ( context.hasIOHandler()) + context.getIOHandler().initialize( this, context); + + // Install the node service monitor + + if ( cfg.getChild("disableNodeMonitor") == null && m_nodeMonitorFactory != null) { + + // Create the node monitor + + NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor( this, context); + context.setNodeMonitor( nodeMonitor); + } + + // Return the context for this shared filesystem + + return context; + } + + /** + * Check if pseudo file support is enabled + * + * @param context ContentContext + * @return boolean + */ + public final boolean hasPseudoFileInterface(ContentContext context) + { + return context.hasPseudoFileInterface(); + } + + /** + * Return the pseudo file support implementation + * + * @param context ContentContext + * @return PseudoFileInterface + */ + public final PseudoFileInterface getPseudoFileInterface(ContentContext context) + { + return context.getPseudoFileInterface(); + } + + /** + * Determine if the disk device is read-only. + * + * @param sess Server session + * @param ctx Device context + * @return boolean + * @exception java.io.IOException If an error occurs. + */ + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException + { + if (cifsHelper.isReadOnly()) + { + return true; + } + else + { + return false; + } + } + + /** + * Get the file information for the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param name File name/path that information is required for. + * @return File information if valid, else null + * @exception java.io.IOException The exception description. + */ + public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException + { + // Start a transaction + + beginReadTransaction( session); + + // Get the device root + + ContentContext ctx = (ContentContext) tree.getContext(); + NodeRef infoParentNodeRef = ctx.getRootNode(); + + if ( path == null || path.length() == 0) + path = FileName.DOS_SEPERATOR_STR; + + String infoPath = path; + + try + { + // Check if the path is to a pseudo file + + FileInfo finfo = null; + + if ( hasPseudoFileInterface(ctx)) + { + // Make sure the parent folder has a file state, and the path exists + + String[] paths = FileName.splitPath( path); + FileState fstate = ctx.getStateTable().findFileState( paths[0]); + + if ( fstate == null) + { + NodeRef nodeRef = getNodeForPath(tree, paths[0]); + + if ( nodeRef != null) + { + // Get the file information for the node + + finfo = cifsHelper.getFileInformation(nodeRef); + } + + // Create the file state + + fstate = ctx.getStateTable().findFileState( paths[0], true, true); + + fstate.setFileStatus( FileStatus.DirectoryExists); + fstate.setNodeRef( nodeRef); + + // Add pseudo files to the folder + + getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added file state for pseudo files folder (getinfo) - " + paths[0]); + } + else if ( fstate.hasPseudoFiles() == false) + { + // Make sure the file state has the node ref + + if ( fstate.hasNodeRef() == false) + { + // Get the node for the folder path + + fstate.setNodeRef( getNodeForPath( tree, paths[0])); + } + + // Add pseudo files for the parent folder + + getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added pseudo files for folder (exists) - " + paths[0]); + } + + + // Get the pseudo file + + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path); + if ( pfile != null) + { + // DEBUG + if ( logger.isDebugEnabled()) + logger.debug("getInfo using pseudo file info for " + path); + + FileInfo pseudoFileInfo = pfile.getFileInfo(); + if (cifsHelper.isReadOnly()) + { + int attr = pseudoFileInfo.getFileAttributes(); + if (( attr & FileAttribute.ReadOnly) == 0) + { + attr += FileAttribute.ReadOnly; + pseudoFileInfo.setFileAttributes(attr); + } + } + return pfile.getFileInfo(); + } + } + + // Get the node ref for the path, chances are there is a file state in the cache + + NodeRef nodeRef = getNodeForPath(tree, infoPath); + + if ( nodeRef != null) + { + // Get the file information for the node + + finfo = cifsHelper.getFileInformation(nodeRef); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("getInfo using cached noderef for path " + path); + } + + // If the required node was not in the state cache, the parent folder node might be + + + if ( finfo == null) + { + String[] paths = FileName.splitPath( path); + + if ( paths[0] != null && paths[0].length() > 1) + { + // Find the node ref for the folder being searched + + nodeRef = getNodeForPath(tree, paths[0]); + + if ( nodeRef != null) + { + infoParentNodeRef = nodeRef; + infoPath = paths[1]; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("getInfo using cached noderef for parent " + path); + } + } + + // Access the repository to get the file information + + finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath); + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug("Getting file information: \n" + + " path: " + path + "\n" + + " file info: " + finfo); + } + } + + // Set the file id for the file using the relative path + + if ( finfo != null) { + + // Set the file id + + finfo.setFileId( path.hashCode()); + + // Copy cached timestamps, if available + + FileState fstate = getStateForPath(tree, infoPath); + if ( fstate != null) { + if ( fstate.hasAccessDateTime()) + finfo.setAccessDateTime(fstate.getAccessDateTime()); + if ( fstate.hasChangeDateTime()) + finfo.setChangeDateTime(fstate.getChangeDateTime()); + if ( fstate.hasModifyDateTime()) + finfo.setModifyDateTime(fstate.getModifyDateTime()); + } + } + + // Return the file information + + return finfo; + } + catch (FileNotFoundException e) + { + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Getting file information - File not found: \n" + + " path: " + path); + throw e; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Get file info - access denied, " + path); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Get file information " + path); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Get file info error", ex); + + // Convert to a general I/O exception + + throw new IOException("Get file information " + path); + } + } + + /** + * Start a new search on the filesystem using the specified searchPath that may contain + * wildcards. + * + * @param sess Server session + * @param tree Tree connection + * @param searchPath File(s) to search for, may include wildcards. + * @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute. + * @return SearchContext + * @exception java.io.FileNotFoundException If the search could not be started. + */ + public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException + { + try + { + // Access the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + + String searchFileSpec = searchPath; + NodeRef searchRootNodeRef = ctx.getRootNode(); + FileState searchFolderState = null; + + // Create the transaction + + beginReadTransaction( sess); + + // If the state table is available see if we can speed up the search using either cached + // file information or find the folder node to be searched without having to walk the path + + String[] paths = FileName.splitPath(searchPath); + + if ( ctx.hasStateTable()) + { + // See if the folder to be searched has a file state, we can avoid having to walk the path + + if ( paths[0] != null && paths[0].length() >= 1) + { + // Find the node ref for the folder being searched + + NodeRef nodeRef = getNodeForPath(tree, paths[0]); + + // Get the file state for the folder being searched + + searchFolderState = getStateForPath(tree, paths[0]); + if ( searchFolderState == null) + { + // Create a file state for the folder + + searchFolderState = ctx.getStateTable().findFileState( paths[0], true, true); + } + + // Make sure the associated node is set + + if ( searchFolderState.hasNodeRef() == false) + { + // Set the associated node for the folder + + searchFolderState.setNodeRef( nodeRef); + } + + // Add pseudo files to the folder being searched + + if ( hasPseudoFileInterface(ctx)) + getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]); + + // Set the search node and file spec + + if ( nodeRef != null) + { + searchRootNodeRef = nodeRef; + searchFileSpec = paths[1]; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Search using cached noderef for path " + searchPath); + } + } + } + + // Convert the all files wildcard + + if ( searchFileSpec.equals( "*.*")) + searchFileSpec = "*"; + + // Perform the search + + List results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec); + + // Check if there are any pseudo files for the folder being searched, for CIFS only + + PseudoFileList pseudoList = null; + + if ( sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles()) + { + // If it is a wildcard search use all pseudo files + + if ( WildCard.containsWildcards(searchFileSpec)) + { + // Get the list of pseudo files for the search path + + pseudoList = searchFolderState.getPseudoFileList(); + + // Check if the wildcard is for all files or a subset + + if ( searchFileSpec.equals( "*") == false && pseudoList != null && pseudoList.numberOfFiles() > 0) + { + // Generate a subset of pseudo files that match the wildcard search pattern + + WildCard wildCard = new WildCard( searchFileSpec, false); + PseudoFileList filterList = null; + + for ( int i = 0; i > pseudoList.numberOfFiles(); i++) + { + PseudoFile pseudoFile = pseudoList.getFileAt( i); + if ( wildCard.matchesPattern( pseudoFile.getFileName())) + { + // Add the pseudo file to the filtered list + + if ( filterList == null) + filterList = new PseudoFileList(); + filterList.addFile( pseudoFile); + } + } + + // Use the filtered pseudo file list, or null if there were no matches + + pseudoList = filterList; + } + } + else if ( results == null || results.size() == 0) + { + // Check if the required file is in the pseudo file list + + String fname = paths[1]; + + if ( fname != null) + { + // Search for a matching pseudo file + + PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true); + if ( pfile != null) + { + // Create a file list with the required file + + pseudoList = new PseudoFileList(); + pseudoList.addFile( pfile); + } + } + } + } + + // Build the search context to store the results + + SearchContext searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0]); + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Started search: \n" + + " search path: " + searchPath + "\n" + + " attributes: " + attributes); + } + return searchCtx; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Start search - access denied, " + searchPath); + + // Convert to a file not found status + + throw new FileNotFoundException("Start search " + searchPath); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Start search", ex); + + // Convert to a file not found status + + throw new FileNotFoundException("Start search " + searchPath); + } + } + + /** + * Check if the specified file exists, and whether it is a file or directory. + * + * @param sess Server session + * @param tree Tree connection + * @param name java.lang.String + * @return int + * @see FileStatus + */ + public int fileExists(SrvSession sess, TreeConnection tree, String name) + { + + int status = FileStatus.Unknown; + + try + { + // Check for a cached file state + + ContentContext ctx = (ContentContext) tree.getContext(); + FileState fstate = null; + + if ( ctx.hasStateTable()) + ctx.getStateTable().findFileState(name); + + if ( fstate != null) + { + FileStateStatus fsts = fstate.getFileStatus(); + + if ( fsts == FileStateStatus.FileExists) + status = FileStatus.FileExists; + else if ( fsts == FileStateStatus.FolderExists) + status = FileStatus.DirectoryExists; + else if ( fsts == FileStateStatus.NotExist || fsts == FileStateStatus.Renamed) + status = FileStatus.NotExist; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Cache hit - fileExists() " + name + ", sts=" + status); + } + else + { + // Check if pseudo files are enabled + + if ( hasPseudoFileInterface(ctx)) + { + // Check if the file name is a pseudo file name + + if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) { + + // Make sure the parent folder has a file state, and the path exists + + String[] paths = FileName.splitPath( name); + fstate = ctx.getStateTable().findFileState( paths[0]); + + if ( fstate == null) { + + // Check if the path exists + + if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists) + { + // Create the file state + + fstate = ctx.getStateTable().findFileState( paths[0], true, true); + + fstate.setFileStatus( FileStatus.DirectoryExists); + + // Get the node for the folder path + + beginReadTransaction( sess); + fstate.setNodeRef( getNodeForPath( tree, paths[0])); + + // Add pseudo files to the folder + + getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added file state for pseudo files folder (exists) - " + paths[0]); + } + } + else if ( fstate.hasPseudoFiles() == false) + { + // Make sure the file state has the node ref + + if ( fstate.hasNodeRef() == false) + { + // Create the transaction + + beginReadTransaction( sess); + + // Get the node for the folder path + + fstate.setNodeRef( getNodeForPath( tree, paths[0])); + } + + // Add pseudo files for the parent folder + + getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added pseudo files for folder (exists) - " + paths[0]); + } + + // Check if the path is to a pseudo file + + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name); + if ( pfile != null) + { + // Indicate that the file exists + + status = FileStatus.FileExists; + } + else + { + // Failed to find pseudo file + + if ( logger.isDebugEnabled()) + logger.debug( "Failed to find pseudo file (exists) - " + name); + } + } + } + + // If the file is not a pseudo file then search for the file + + if ( status == FileStatus.Unknown) + { + // Create the transaction + + beginReadTransaction( sess); + + // Get the file information to check if the file/folder exists + + FileInfo info = getFileInformation(sess, tree, name); + if (info.isDirectory()) + { + status = FileStatus.DirectoryExists; + } + else + { + status = FileStatus.FileExists; + } + } + } + } + catch (FileNotFoundException e) + { + status = FileStatus.NotExist; + } + catch (IOException e) + { + // Debug + + logger.debug("File exists error, " + name, e); + + status = FileStatus.NotExist; + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("File status determined: \n" + + " name: " + name + "\n" + + " status: " + status); + } + + // Return the file/folder status + + return status; + } + + /** + * Open a file or folder + * + * @param sess SrvSession + * @param tree TreeConnection + * @param params FileOpenParams + * @return NetworkFile + * @exception IOException + */ + public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException + { + // Create the transaction + + beginReadTransaction( sess); + + try + { + // Get the node for the path + + ContentContext ctx = (ContentContext) tree.getContext(); + + // Check if pseudo files are enabled + + if ( hasPseudoFileInterface(ctx)) + { + // Check if the file name is a pseudo file name + + String path = params.getPath(); + + if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, path)) { + + // Make sure the parent folder has a file state, and the path exists + + String[] paths = FileName.splitPath( path); + FileState fstate = ctx.getStateTable().findFileState( paths[0]); + + if ( fstate == null) { + + // Check if the path exists + + if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists) + { + // Create the file state and add any pseudo files + + fstate = ctx.getStateTable().findFileState( paths[0], true, true); + + fstate.setFileStatus( FileStatus.DirectoryExists); + getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added file state for pseudo files folder (open) - " + paths[0]); + } + } + else if ( fstate.hasPseudoFiles() == false) + { + // Add pseudo files for the parent folder + + getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug( "Added pseudo files for folder (open) - " + paths[0]); + } + + // Check if the path is to a pseudo file + + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath()); + if ( pfile != null) + { + // Create a network file to access the pseudo file data + + return pfile.getFile( params.getPath()); + } + else + { + // Failed to find pseudo file + + if ( logger.isDebugEnabled()) + logger.debug( "Failed to find pseudo file (open) - " + params.getPath()); + } + } + } + + // Not a pseudo file, try and open a normal file/folder node + + NodeRef nodeRef = getNodeForPath(tree, params.getPath()); + + // Check permissions on the file/folder node + // + // Check for read access + + if ( params.hasAccessMode(AccessMode.NTRead) && + permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) + throw new AccessDeniedException("No read access to " + params.getFullPath()); + + // Check for write access + + if ( params.hasAccessMode(AccessMode.NTWrite) && + permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) + throw new AccessDeniedException("No write access to " + params.getFullPath()); + + // Check for delete access + +// if ( params.hasAccessMode(AccessMode.NTDelete) && +// permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) +// throw new AccessDeniedException("No delete access to " + params.getFullPath()); + + // Check if the file has a lock + + String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE); + + if ( params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null) + throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath()); + + // Check if there is a file state for the file + + FileState fstate = null; + + if ( ctx.hasStateTable()) + { + // Check if there is a file state for the file + + fstate = ctx.getStateTable().findFileState( params.getPath()); + + if ( fstate != null) + { + // Check if the file exists + + if ( fstate.exists() == false) + throw new FileNotFoundException(); + + // Check if the open request shared access indicates exclusive file access + + if ( fstate != null && params.getSharedAccess() == SharingMode.NOSHARING && + fstate.getOpenCount() > 0) + throw new FileSharingException("File already open, " + params.getPath()); + } + } + + // Check if the node is a link node + + NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); + AlfrescoNetworkFile netFile = null; + + if ( linkRef == null) + { + // Create the network file + + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params); + } + else + { + // Get the CIFS server name + + String srvName = null; + SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer( "CIFS"); + + if ( cifsServer != null) + { + // Use the CIFS server name in the URL + + srvName = cifsServer.getServerName(); + } + else + { + // Use the local server name in the URL + + srvName = InetAddress.getLocalHost().getHostName(); + } + + // Convert the target node to a path, convert to URL format + + String path = getPathForNode( tree, linkRef); + path = path.replace( FileName.DOS_SEPERATOR, '/'); + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL=file://"); + urlStr.append( srvName); + urlStr.append("/"); + urlStr.append( tree.getSharedDevice().getName()); + urlStr.append( path); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + // Get the file information for the link node + + FileInfo fInfo = cifsHelper.getFileInformation( nodeRef); + + // Set the file size to the actual data length + + fInfo.setFileSize( urlData.length); + + // Create the network file using the in-memory file data + + netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef); + netFile.setFullName( params.getPath()); + } + + // Generate a file id for the file + + if ( netFile != null) + netFile.setFileId( params.getPath().hashCode()); + + // Create a file state for the open file + + if ( ctx.hasStateTable()) + { + if ( fstate == null) + fstate = ctx.getStateTable().findFileState(params.getPath(), params.isDirectory(), true); + + // Update the file state, cache the node + + fstate.incrementOpenCount(); + fstate.setNodeRef(nodeRef); + + // Store the state with the file + + netFile.setFileState( fstate); + } + + // If the file has been opened for overwrite then truncate the file to zero length, this will + // also prevent the existing content data from being copied to the new version of the file + + if ( params.isOverwrite() && netFile != null) + { + // Truncate the file to zero length + + netFile.truncateFile( 0L); + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Opened network file: \n" + + " path: " + params.getPath() + "\n" + + " file open parameters: " + params + "\n" + + " network file: " + netFile); + } + + // Return the network file + + return netFile; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Open file - access denied, " + params.getFullPath()); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Open file " + params.getFullPath()); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Open file error", ex); + + // Convert to a general I/O exception + + throw new IOException("Open file " + params.getFullPath()); + } + } + + /** + * Create a new file on the file system. + * + * @param sess Server session + * @param tree Tree connection + * @param params File create parameters + * @return NetworkFile + * @exception java.io.IOException If an error occurs. + */ + public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + try + { + // Get the device root + + ContentContext ctx = (ContentContext) tree.getContext(); + NodeRef deviceRootNodeRef = ctx.getRootNode(); + + String path = params.getPath(); + + // If the state table is available then try to find the parent folder node for the new file + // to save having to walk the path + + if ( ctx.hasStateTable()) + { + // See if the parent folder has a file state, we can avoid having to walk the path + + String[] paths = FileName.splitPath(path); + if ( paths[0] != null && paths[0].length() > 1) + { + // Find the node ref for the folder being searched + + NodeRef nodeRef = getNodeForPath(tree, paths[0]); + + if ( nodeRef != null) + { + deviceRootNodeRef = nodeRef; + path = paths[1]; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Create file using cached noderef for path " + paths[0]); + } + } + } + + // Create it - the path will be created, if necessary + + NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, true); + + // Create the network file + + ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params); + + // Always allow write access to a newly created file + + netFile.setGrantedAccess(NetworkFile.READWRITE); + + // Truncate the file so that the content stream is created + + netFile.truncateFile( 0L); + + // Generate a file id for the file + + if ( netFile != null) + netFile.setFileId( params.getPath().hashCode()); + + // Add a file state for the new file/folder + + if ( ctx.hasStateTable()) + { + FileState fstate = ctx.getStateTable().findFileState(params.getPath(), false, true); + if ( fstate != null) + { + // Indicate that the file is open + + fstate.setFileStatus(FileStateStatus.FileExists); + fstate.incrementOpenCount(); + fstate.setNodeRef(nodeRef); + + // Store the file state with the file + + netFile.setFileState( fstate); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Create file, state=" + fstate); + } + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Created file: \n" + + " path: " + path + "\n" + + " file open parameters: " + params + "\n" + + " node: " + nodeRef + "\n" + + " network file: " + netFile); + } + + return netFile; + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Create file - access denied, " + params.getFullPath()); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Create file " + params.getFullPath()); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Create file error", ex); + + // Convert to a general I/O exception + + throw new IOException("Create file " + params.getFullPath()); + } + + } + + /** + * Create a new directory on this file system. + * + * @param sess Server session + * @param tree Tree connection. + * @param params Directory create parameters + * @exception java.io.IOException If an error occurs. + */ + public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + try + { + // get the device root + + ContentContext ctx = (ContentContext) tree.getContext(); + NodeRef deviceRootNodeRef = ctx.getRootNode(); + + String path = params.getPath(); + + // If the state table is available then try to find the parent folder node for the new folder + // to save having to walk the path + + if ( ctx.hasStateTable()) + { + // See if the parent folder has a file state, we can avoid having to walk the path + + String[] paths = FileName.splitPath(path); + if ( paths[0] != null && paths[0].length() > 1) + { + // Find the node ref for the folder being searched + + NodeRef nodeRef = getNodeForPath(tree, paths[0]); + + if ( nodeRef != null) + { + deviceRootNodeRef = nodeRef; + path = paths[1]; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Create file using cached noderef for path " + paths[0]); + } + } + } + + // Create it - the path will be created, if necessary + + NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, false); + + // Add a file state for the new folder + + if ( ctx.hasStateTable()) + { + FileState fstate = ctx.getStateTable().findFileState( params.getPath(), true, true); + if ( fstate != null) + { + // Indicate that the file is open + + fstate.setFileStatus(FileStateStatus.FolderExists); + fstate.incrementOpenCount(); + fstate.setNodeRef(nodeRef); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Creaste folder, state=" + fstate); + } + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Created directory: \n" + + " path: " + path + "\n" + + " file open params: " + params + "\n" + + " node: " + nodeRef); + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Create directory - access denied, " + params.getFullPath()); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Create directory " + params.getFullPath()); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Create directory error", ex); + + // Convert to a general I/O exception + + throw new IOException("Create directory " + params.getFullPath()); + } + } + + /** + * Delete the directory from the filesystem. + * + * @param sess Server session + * @param tree Tree connection + * @param dir Directory name. + * @exception java.io.IOException The exception description. + */ + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + // get the device root + + ContentContext ctx = (ContentContext) tree.getContext(); + NodeRef deviceRootNodeRef = ctx.getRootNode(); + + try + { + // Get the node for the folder + + NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir); + if (fileFolderService.exists(nodeRef)) + { + // Check if the folder is empty + + if ( cifsHelper.isFolderEmpty( nodeRef) == true) { + + // Delete the folder node + + fileFolderService.delete(nodeRef); + + // Remove the file state + + if ( ctx.hasStateTable()) + ctx.getStateTable().removeFileState(dir); + } + else + throw new DirectoryNotEmptyException( dir); + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted directory: \n" + + " directory: " + dir + "\n" + + " node: " + nodeRef); + } + } + catch (FileNotFoundException e) + { + // already gone + if (logger.isDebugEnabled()) + { + logger.debug("Deleted directory : \n" + + " directory: " + dir); + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete directory - access denied, " + dir); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Delete directory " + dir); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete directory", ex); + + // Convert to a general I/O exception + + throw new IOException("Delete directory " + dir); + } + } + + /** + * Flush any buffered output for the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file context. + * @exception java.io.IOException The exception description. + */ + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException + { + // Flush the file data + + file.flushFile(); + } + + /** + * Close the file. + * + * @param sess Server session + * @param tree Tree connection. + * @param param Network file context. + * @exception java.io.IOException If an error occurs. + */ + public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + // Get the associated file state + + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( ctx.hasStateTable()) + { + FileState fstate = ctx.getStateTable().findFileState(file.getFullName()); + if ( fstate != null) + fstate.decrementOpenCount(); + } + + // Defer to the network file to close the stream and remove the content + + file.closeFile(); + + // Remove the node if marked for delete + + if (file.hasDeleteOnClose()) + { + // Check if the file is a noderef based file + + if ( file instanceof NodeRefNetworkFile) + { + NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; + NodeRef nodeRef = nodeNetFile.getNodeRef(); + + // We don't know how long the network file has had the reference, so check for existence + + if (fileFolderService.exists(nodeRef)) + { + try + { + // Delete the file + + fileFolderService.delete(nodeRef); + + // Remove the file state + + if ( ctx.hasStateTable()) + ctx.getStateTable().removeFileState(file.getFullName()); + + // Commit the current transaction + +// sess.endTransaction(); +// beginReadTransaction( sess); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete on close - access denied, " + file.getFullName()); + + // Convert to a filesystem access denied exception + + throw new AccessDeniedException("Delete on close " + file.getFullName()); + } + } + } + else if ( file instanceof PseudoNetworkFile || + file instanceof MemoryNetworkFile) + { + // Delete the pseudo file + + if ( hasPseudoFileInterface(ctx)) + { + // Delete the pseudo file + + getPseudoFileInterface(ctx).deletePseudoFile( sess, tree, file.getFullName()); + } + } + } + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug("Closed file: \n" + + " network file: " + file + "\n" + + " deleted on close: " + file.hasDeleteOnClose()); + } + } + + /** + * Delete the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param file NetworkFile + * @exception java.io.IOException The exception description. + */ + public void deleteFile(SrvSession sess, TreeConnection tree, String name) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + // Get the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + + try + { + // Get the node + + NodeRef nodeRef = getNodeForPath(tree, name); + if (fileFolderService.exists(nodeRef)) + { + fileFolderService.delete(nodeRef); + + // Remove the file state + + if ( ctx.hasStateTable()) + ctx.getStateTable().removeFileState(name); + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted file: \n" + + " file: " + name + "\n" + + " node: " + nodeRef); + } + } + catch (NodeLockedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete file - access denied (locked)"); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Delete " + name); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete file - access denied"); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Delete " + name); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Delete file error", ex); + + // Convert to a general I/O exception + + throw new IOException("Delete file " + name); + } + } + + /** + * Rename the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param oldName java.lang.String + * @param newName java.lang.String + * @exception java.io.IOException The exception description. + */ + public void renameFile(SrvSession sess, TreeConnection tree, String oldName, String newName) throws IOException + { + // Create the transaction + + beginWriteTransaction( sess); + + try + { + // Get the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + + // Get the file/folder to move + + NodeRef nodeToMoveRef = getNodeForPath(tree, oldName); + + // Check if the node is a link node + + if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) + throw new AccessDeniedException("Cannot rename link nodes"); + + // Get the new target folder - it must be a folder + + String[] splitPaths = FileName.splitPath(newName); + NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]); + String name = splitPaths[1]; + + // Check if this is a rename within the same folder + + String[] oldPaths = FileName.splitPath( oldName); + + boolean sameFolder = false; + if ( splitPaths[0].equalsIgnoreCase( oldPaths[0])) + sameFolder = true; + + // Update the state table + + boolean relinked = false; + if ( ctx.hasStateTable()) + { + // Check if the file rename can be relinked to a previous version + + if ( !cifsHelper.isDirectory(nodeToMoveRef) ) + { + // Check if there is a renamed file state for the new file name + + FileState renState = ctx.getStateTable().removeFileState(newName); + + if ( renState != null && renState.getFileStatus() == FileStateStatus.Renamed) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug(" Found rename state, relinking, " + renState); + + // Relink the new version of the file data to the previously renamed node so that it + // picks up version history and other settings. + + cifsHelper.relinkNode( renState.getNodeRef(), nodeToMoveRef, targetFolderRef, name); + relinked = true; + + // Link the node ref for the associated rename state + + if ( renState.hasRenameState()) + renState.getRenameState().setNodeRef(nodeToMoveRef); + + // Remove the file state for the old file name + + ctx.getStateTable().removeFileState(oldName); + + // Get, or create, a file state for the new file path + + FileState fstate = ctx.getStateTable().findFileState(newName, false, true); + + fstate.setNodeRef(renState.getNodeRef()); + fstate.setFileStatus(FileStateStatus.FileExists); + } + } + else + { + // Get the file state for the folder, if available + + FileState fstate = ctx.getStateTable().findFileState(oldName); + + if ( fstate != null) + { + // Update the file state index to use the new name + + ctx.getStateTable().renameFileState(newName, fstate); + } + } + } + + // Move the file/folder, if not relinked to previous version history + + if (!relinked) + { + // Move or rename the file/folder + + if ( sameFolder == true) + { + // Check if the new file name is a temporary file name + + String newNameNorm = newName.toLowerCase(); + boolean isTempFile = false; + + if ( newNameNorm.endsWith(".tmp") || newNameNorm.endsWith(".temp")) { + + // Add the temporary aspect, also prevents versioning + + nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_TEMPORARY, null); + + // Indicate that the new file is a temporary file + + isTempFile = true; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Added Temporary aspect to renamed file " + newName); + } + + // Rename the file/folder + + cifsHelper.rename(nodeToMoveRef, name); + + // Check if the temporary aspect should be removed from the renamed file + + String oldNameNorm = oldName.toLowerCase(); + + if ( isTempFile == false && (oldNameNorm.endsWith(".tmp") || oldNameNorm.endsWith(".temp"))) { + + // Remove the temporary aspect + + nodeService.removeAspect(nodeToMoveRef, ContentModel.ASPECT_TEMPORARY); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Removed Temporary aspect from renamed file " + newName); + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Renamed file: from: " + oldName + " to: " + newName); + } + else + { + // Move the file/folder + + cifsHelper.move(nodeToMoveRef, targetFolderRef, name); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Moved file: from: " + oldName + " to: " + newName); + } + + // Check if we renamed a file, if so then cache the rename details for a short period + // in case another file renamed to the old name. MS Word uses renames to move a new + // version of a document into place so we need to reconnect the version history. + + if ( !cifsHelper.isDirectory(nodeToMoveRef)) + { + // Get or create a new file state for the old file path + + FileState fstate = ctx.getStateTable().findFileState(oldName, false, true); + + // Make sure the file state is cached for a short while, the file may not be open so the + // file state could be expired + + fstate.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout); + + // Indicate that this is a renamed file state, set the node ref of the file that was renamed + + fstate.setFileStatus(FileStateStatus.Renamed); + fstate.setNodeRef(nodeToMoveRef); + + // Get, or create, a file state for the new file path + + FileState newState = ctx.getStateTable().findFileState(newName, false, true); + + newState.setNodeRef(nodeToMoveRef); + newState.setFileStatus(FileStateStatus.FileExists); + + // Link the renamed state to the new state + + fstate.setRenameState(newState); + + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug("Cached rename state for " + oldName + ", state=" + fstate); + logger.debug(" new name " + newName + ", state=" + newState); + } + } + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Moved node: from: " + oldName + " to: " + newName); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Rename file - access denied, " + oldName); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Rename file " + oldName); + } + catch (NodeLockedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Rename file", ex); + + // Convert to an filesystem access denied exception + + throw new AccessDeniedException("Node locked " + oldName); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Rename file", ex); + + // Convert to a general I/O exception + + throw new AccessDeniedException("Rename file " + oldName); + } + } + + /** + * Set file information + * + * @param sess SrvSession + * @param tree TreeConnection + * @param name String + * @param info FileInfo + * @exception IOException + */ + public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) throws IOException + { + try + { + // Get the device context + + ContentContext ctx = (ContentContext) tree.getContext(); + + // Check if pseudo files are enabled + + if ( hasPseudoFileInterface(ctx) && + getPseudoFileInterface(ctx).isPseudoFile( sess, tree, name)) + { + // Allow the file information to be changed + + return; + } + + // Get the file/folder node + + NodeRef nodeRef = getNodeForPath(tree, name); + FileState fstate = getStateForPath(tree, name); + + // Check permissions on the file/folder node + + if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) + throw new AccessDeniedException("No write access to " + name); + + if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) + throw new AccessDeniedException("No delete access to " + name); + + // Check if the file is being marked for deletion, if so then check if the file is locked + + if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) + { + // Start a transaction + + beginReadTransaction( sess); + + // Check if the node is locked + + if ( nodeService.hasAspect( nodeRef, ContentModel.ASPECT_LOCKABLE)) + { + // Get the lock type, if any + + String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE); + + if ( lockTypeStr != null) + throw new AccessDeniedException("Node locked, cannot mark for delete"); + } + + // Update the change date/time + + if ( fstate != null) + fstate.updateChangeDateTime(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Set deleteOnClose=true file=" + name); + } + + // Set the creation date/time + + if ( info.hasSetFlag(FileInfo.SetCreationDate)) { + + // Create the transaction + + beginWriteTransaction( sess); + + // Set the creation date on the file/folder node + + Date createDate = new Date( info.getCreationDateTime()); + nodeService.setProperty( nodeRef, ContentModel.PROP_CREATED, createDate); + + // Update the change date/time + + if ( fstate != null) + fstate.updateChangeDateTime(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Set creationDate=" + createDate + " file=" + name); + } + + // Set the modification date/time + + if ( info.hasSetFlag(FileInfo.SetModifyDate)) { + + // Create the transaction + + beginWriteTransaction( sess); + + // Set the creation date on the file/folder node + + Date modifyDate = new Date( info.getModifyDateTime()); + nodeService.setProperty( nodeRef, ContentModel.PROP_MODIFIED, modifyDate); + + // Update the change date/time + + if ( fstate != null) + fstate.updateChangeDateTime(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Set modifyDate=" + modifyDate + " file=" + name); + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Set file information - access denied, " + name); + + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Set file information " + name); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Open file error", ex); + + // Convert to a general I/O exception + + throw new IOException("Set file information " + name); + } + } + + /** + * Truncate a file to the specified size + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file details + * @param size New file length + * @exception java.io.IOException The exception description. + */ + public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException + { + // Truncate or extend the file to the required size + + file.truncateFile(size); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Truncated file: \n" + + " network file: " + file + "\n" + + " size: " + size); + } + } + + /** + * Read a block of data from the specified file. + * + * @param sess Session details + * @param tree Tree connection + * @param file Network file + * @param buf Buffer to return data to + * @param bufPos Starting position in the return buffer + * @param siz Maximum size of data to return + * @param filePos File offset to read data + * @return Number of bytes read + * @exception java.io.IOException The exception description. + */ + public int readFile( + SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException + { + // Check if the file is a directory + + if(file.isDirectory()) + throw new AccessDeniedException(); + + // If the content channel is not open for the file then start a transaction + + if ( file instanceof ContentNetworkFile) + { + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); + } + + // Read a block of data from the file + + int count = file.readFile(buffer, size, bufferPosition, fileOffset); + + if ( count == -1) + { + // Read count of -1 indicates a read past the end of file + + count = 0; + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Read bytes from file: \n" + + " network file: " + file + "\n" + + " buffer size: " + buffer.length + "\n" + + " buffer pos: " + bufferPosition + "\n" + + " size: " + size + "\n" + + " file offset: " + fileOffset + "\n" + + " bytes read: " + count); + } + return count; + } + + /** + * Seek to the specified file position. + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file. + * @param pos Position to seek to. + * @param typ Seek type. + * @return New file position, relative to the start of file. + */ + public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException + { + // Check if the file is a directory + + if ( file.isDirectory()) + throw new AccessDeniedException(); + + // If the content channel is not open for the file then start a transaction + + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); + + // Set the file position + + return file.seekFile(pos, typ); + } + + /** + * Write a block of data to the file. + * + * @param sess Server session + * @param tree Tree connection + * @param file Network file details + * @param buf byte[] Data to be written + * @param bufoff Offset within the buffer that the data starts + * @param siz int Data length + * @param fileoff Position within the file that the data is to be written. + * @return Number of bytes actually written + * @exception java.io.IOException The exception description. + */ + public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException + { + // If the content channel is not open for the file then start a transaction + + if ( file instanceof ContentNetworkFile) + { + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); + } + + // Write to the file + + file.writeFile(buffer, size, bufferOffset, fileOffset); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Wrote bytes to file: \n" + + " network file: " + file + "\n" + + " buffer size: " + buffer.length + "\n" + + " size: " + size + "\n" + + " file offset: " + fileOffset); + } + return size; + } + + /** + * Get the node for the specified path + * + * @param tree TreeConnection + * @param path String + * @return NodeRef + * @exception FileNotFoundException + */ + public NodeRef getNodeForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + // Check if there is a cached state for the path + + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( ctx.hasStateTable()) + { + // Try and get the node ref from an in memory file state + + FileState fstate = ctx.getStateTable().findFileState(path); + if ( fstate != null && fstate.hasNodeRef() && fstate.exists() ) + { + // Check that the node exists + + if (fileFolderService.exists(fstate.getNodeRef())) + { + // Bump the file states expiry time + + fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout); + + // Return the cached noderef + + return fstate.getNodeRef(); + } + else + { + ctx.getStateTable().removeFileState(path); + } + } + } + + // Search the repository for the node + + return cifsHelper.getNodeRef(ctx.getRootNode(), path); + } + + /** + * Convert a node into a share relative path + * + * @param tree TreeConnection + * @param nodeRef NodeRef + * @return String + * @exception FileNotFoundException + */ + public String getPathForNode( TreeConnection tree, NodeRef nodeRef) + throws FileNotFoundException + { + // Convert the target node to a path + + ContentContext ctx = (ContentContext) tree.getContext(); + List linkPaths = null; + + try { + linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); + } + catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) + { + throw new FileNotFoundException(); + } + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + + for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) { + pathStr.append( FileName.DOS_SEPERATOR); + pathStr.append( fInfo.getName()); + } + + // Return the share relative path + + return pathStr.toString(); + } + + /** + * Get the file state for the specified path + * + * @param tree TreeConnection + * @param path String + * @return FileState + * @exception FileNotFoundException + */ + public FileState getStateForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + // Check if there is a cached state for the path + + ContentContext ctx = (ContentContext) tree.getContext(); + FileState fstate = null; + + if ( ctx.hasStateTable()) + { + // Get the file state for a file/folder + + fstate = ctx.getStateTable().findFileState(path); + } + + // Return the file state + + return fstate; + } + + /** + * Connection opened to this disk device + * + * @param sess Server session + * @param tree Tree connection + */ + public void treeClosed(SrvSession sess, TreeConnection tree) + { + // Nothing to do + } + + /** + * Connection closed to this device + * + * @param sess Server session + * @param tree Tree connection + */ + public void treeOpened(SrvSession sess, TreeConnection tree) + { + // Nothing to do + } + + /** + * Return the lock manager used by this filesystem + * + * @param sess SrvSession + * @param tree TreeConnection + * @return LockManager + */ + public LockManager getLockManager(SrvSession sess, TreeConnection tree) { + return _lockManager; + } +} diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index a11972ebba..11276214e7 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -37,7 +37,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -516,8 +516,8 @@ public class NodeMonitor extends TransactionListenerAdapter // Use the system user as the authenticated context for the node monitor - AuthenticationComponent authComponent = m_filesysDriver.getAuthComponent(); - authComponent.setCurrentUser( authComponent.getSystemUserName()); + AuthenticationContext authenticationContext = m_filesysDriver.getAuthenticationContext(); + authenticationContext.setSystemUserAsCurrentUser(); // Loop until shutdown diff --git a/source/java/org/alfresco/jcr/test/TestData.java b/source/java/org/alfresco/jcr/test/TestData.java index ed0075cfb7..98ed717546 100644 --- a/source/java/org/alfresco/jcr/test/TestData.java +++ b/source/java/org/alfresco/jcr/test/TestData.java @@ -1,148 +1,148 @@ -/* - * 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.jcr.test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.MutableAuthenticationDao; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.transaction.TransactionService; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - - - -public class TestData -{ - public static final String TEST_WORKSPACE = "test"; - - /** - * Generate Test Workspace within Repository - * - * @param args - */ - public static void main(String[] args) - { - ApplicationContext context = new ClassPathXmlApplicationContext("org/alfresco/jcr/test/test-context.xml"); - generateTestData(context, TEST_WORKSPACE); - System.out.println("Generated TCK test data to workspace: " + TEST_WORKSPACE); - System.exit(0); - } - - /** - * Bootstrap Repository with JCR Test Data - * - * @param applicationContext - * @param workspaceName - */ - public static void generateTestData(final ApplicationContext applicationContext, String workspaceName) - { - final ServiceRegistry serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); - TransactionService transactionService = serviceRegistry.getTransactionService(); - RetryingTransactionCallback createUserWork = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - // Bootstrap Users - MutableAuthenticationDao authDAO = (MutableAuthenticationDao) applicationContext.getBean("authenticationDao"); - if (authDAO.userExists("superuser") == false) - { - authDAO.createUser("superuser", "".toCharArray()); - } - if (authDAO.userExists("user") == false) - { - authDAO.createUser("user", "".toCharArray()); - } - if (authDAO.userExists("anonymous") == false) - { - authDAO.createUser("anonymous", "".toCharArray()); - } - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction(createUserWork); - - try - { - AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); - authenticationComponent.setSystemUserAsCurrentUser(); - - try - { - // Bootstrap Workspace Test Data - StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, workspaceName); - - ImporterBootstrap bootstrap = new ImporterBootstrap(); - bootstrap.setAuthenticationComponent((AuthenticationComponent) applicationContext.getBean("authenticationComponent")); - bootstrap.setImporterService((ImporterService) applicationContext.getBean(ServiceRegistry.IMPORTER_SERVICE.getLocalName())); - bootstrap.setNodeService((NodeService) applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName())); - bootstrap.setNamespaceService((NamespaceService) applicationContext.getBean(ServiceRegistry.NAMESPACE_SERVICE.getLocalName())); - bootstrap.setTransactionService((TransactionService) applicationContext.getBean(ServiceRegistry.TRANSACTION_SERVICE.getLocalName())); - bootstrap.setStoreUrl(storeRef.toString()); - - List views = new ArrayList(); - Properties testView = new Properties(); - testView.setProperty("path", "/"); - testView.setProperty("location", "org/alfresco/jcr/test/testData.xml"); - views.add(testView); - bootstrap.setBootstrapViews(views); - bootstrap.bootstrap(); - - // Bootstrap clears security context - authenticationComponent.setSystemUserAsCurrentUser(); - - PermissionService permissionService = (PermissionService)applicationContext.getBean(ServiceRegistry.PERMISSIONS_SERVICE.getLocalName()); - NodeService nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); - -// permissionService.setPermission(nodeService.getRootNode(storeRef), PermissionService.ALL_AUTHORITIES, PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(nodeService.getRootNode(storeRef), "superuser", PermissionService.ALL_PERMISSIONS, true); - permissionService.setPermission(nodeService.getRootNode(storeRef), "anonymous", PermissionService.READ, true); - permissionService.setPermission(nodeService.getRootNode(storeRef), "user", PermissionService.READ, true); - permissionService.setPermission(nodeService.getRootNode(storeRef), "user", PermissionService.WRITE, true); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - } - catch (RuntimeException e) - { - System.out.println("Exception: " + e); - e.printStackTrace(); - throw e; - } - } - -} +/* + * 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.jcr.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + + + +public class TestData +{ + public static final String TEST_WORKSPACE = "test"; + + /** + * Generate Test Workspace within Repository + * + * @param args + */ + public static void main(String[] args) + { + ApplicationContext context = new ClassPathXmlApplicationContext("org/alfresco/jcr/test/test-context.xml"); + generateTestData(context, TEST_WORKSPACE); + System.out.println("Generated TCK test data to workspace: " + TEST_WORKSPACE); + System.exit(0); + } + + /** + * Bootstrap Repository with JCR Test Data + * + * @param applicationContext + * @param workspaceName + */ + public static void generateTestData(final ApplicationContext applicationContext, String workspaceName) + { + final ServiceRegistry serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + TransactionService transactionService = serviceRegistry.getTransactionService(); + RetryingTransactionCallback createUserWork = new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Bootstrap Users + MutableAuthenticationDao authDAO = (MutableAuthenticationDao) applicationContext.getBean("authenticationDao"); + if (authDAO.userExists("superuser") == false) + { + authDAO.createUser("superuser", "".toCharArray()); + } + if (authDAO.userExists("user") == false) + { + authDAO.createUser("user", "".toCharArray()); + } + if (authDAO.userExists("anonymous") == false) + { + authDAO.createUser("anonymous", "".toCharArray()); + } + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(createUserWork); + + try + { + AuthenticationContext authenticationContext = (AuthenticationContext)applicationContext.getBean("authenticationContext"); + authenticationContext.setSystemUserAsCurrentUser(); + + try + { + // Bootstrap Workspace Test Data + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, workspaceName); + + ImporterBootstrap bootstrap = new ImporterBootstrap(); + bootstrap.setAuthenticationContext((AuthenticationContext) applicationContext.getBean("authenticationContext")); + bootstrap.setImporterService((ImporterService) applicationContext.getBean(ServiceRegistry.IMPORTER_SERVICE.getLocalName())); + bootstrap.setNodeService((NodeService) applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName())); + bootstrap.setNamespaceService((NamespaceService) applicationContext.getBean(ServiceRegistry.NAMESPACE_SERVICE.getLocalName())); + bootstrap.setTransactionService((TransactionService) applicationContext.getBean(ServiceRegistry.TRANSACTION_SERVICE.getLocalName())); + bootstrap.setStoreUrl(storeRef.toString()); + + List views = new ArrayList(); + Properties testView = new Properties(); + testView.setProperty("path", "/"); + testView.setProperty("location", "org/alfresco/jcr/test/testData.xml"); + views.add(testView); + bootstrap.setBootstrapViews(views); + bootstrap.bootstrap(); + + // Bootstrap clears security context + authenticationContext.setSystemUserAsCurrentUser(); + + PermissionService permissionService = (PermissionService)applicationContext.getBean(ServiceRegistry.PERMISSIONS_SERVICE.getLocalName()); + NodeService nodeService = (NodeService)applicationContext.getBean(ServiceRegistry.NODE_SERVICE.getLocalName()); + +// permissionService.setPermission(nodeService.getRootNode(storeRef), PermissionService.ALL_AUTHORITIES, PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(nodeService.getRootNode(storeRef), "superuser", PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission(nodeService.getRootNode(storeRef), "anonymous", PermissionService.READ, true); + permissionService.setPermission(nodeService.getRootNode(storeRef), "user", PermissionService.READ, true); + permissionService.setPermission(nodeService.getRootNode(storeRef), "user", PermissionService.WRITE, true); + } + finally + { + authenticationContext.clearCurrentSecurityContext(); + } + } + catch (RuntimeException e) + { + System.out.println("Exception: " + e); + e.printStackTrace(); + throw e; + } + } + +} diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index c292fd3a3f..4582be8b44 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -1,1597 +1,1597 @@ -/* - * 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.repo.action; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.evaluator.ActionConditionEvaluator; -import org.alfresco.repo.action.executer.ActionExecuter; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionConditionDefinition; -import org.alfresco.service.cmr.action.ActionDefinition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.action.CompositeAction; -import org.alfresco.service.cmr.action.CompositeActionCondition; -import org.alfresco.service.cmr.action.ParameterizedItem; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.GUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Action service implementation - * - * @author Roy Wetherall - */ -public class ActionServiceImpl implements ActionService, RuntimeActionService, ApplicationContextAware -{ - /** - * Transaction resource name - */ - private static final String POST_TRANSACTION_PENDING_ACTIONS = "postTransactionPendingActions"; - - /** - * Error message - */ - private static final String ERR_FAIL = "The action failed to execute due to an error."; - - /** - * The logger - */ - private static Log logger = LogFactory.getLog(ActionServiceImpl.class); - - /** - * Thread local containing the current action chain - */ - ThreadLocal> currentActionChain = new ThreadLocal>(); - - /** - * The application context - */ - private ApplicationContext applicationContext; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The search service - */ - private SearchService searchService; - - /** The dictionary service */ - private DictionaryService dictionaryService; - - /** The authentication component */ - private AuthenticationComponent authenticationComponent; - - /** - * The asynchronous action execution queues - * map of name, queue - */ - private Map asynchronousActionExecutionQueues; - - /** - * Action transaction listener - */ - private ActionTransactionListener transactionListener = new ActionTransactionListener(this); - - /** - * All the condition definitions currently registered - */ - private Map conditionDefinitions = new HashMap(); - - /** - * All the action definitions currently registered - */ - private Map actionDefinitions = new HashMap(); - - /** - * Set the application context - * - * @param applicationContext the application context - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the search service - * - * @param searchService the search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Set the authentication component - * - * @param authenticationComponent the authentication component - */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - /** - * Set the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the asynchronous action execution queues - * - * @param asynchronousActionExecutionQueue the asynchronous action execution queues - */ - public void setAsynchronousActionExecutionQueues( - Map asynchronousActionExecutionQueues) - { - this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; - } - -// /** -// * Get the asynchronous action execution queue -// * -// * @return the asynchronous action execution queue -// */ -// public AsynchronousActionExecutionQueue getAsynchronousActionExecutionQueue() -// { -// return asynchronousActionExecutionQueue; -// } -// - /** - * Gets the saved action folder reference - * - * @param nodeRef the node reference - * @return the node reference - */ - private NodeRef getSavedActionFolderRef(NodeRef nodeRef) - { - List assocs = this.nodeService.getChildAssocs( - nodeRef, - RegexQNamePattern.MATCH_ALL, - ActionModel.ASSOC_ACTION_FOLDER); - if (assocs.size() != 1) - { - throw new ActionServiceException("Unable to retrieve the saved action folder reference."); - } - - return assocs.get(0).getChildRef(); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinition(java.lang.String) - */ - public ActionDefinition getActionDefinition(String name) - { - // get direct access to action definition (i.e. ignoring public flag of executer) - ActionDefinition definition = null; - Object bean = this.applicationContext.getBean(name); - if (bean != null && bean instanceof ActionExecuter) - { - ActionExecuter executer = (ActionExecuter)bean; - definition = executer.getActionDefinition(); - } - return definition; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions() - */ - public List getActionDefinitions() - { - return new ArrayList(this.actionDefinitions.values()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions(org.alfresco.service.cmr.repository.NodeRef) - */ - public List getActionDefinitions(NodeRef nodeRef) - { - if (nodeRef == null) - { - return getActionDefinitions(); - } - else - { - // TODO for now we will only filter by type, we will introduce filtering by aspect later - QName nodeType = this.nodeService.getType(nodeRef); - List result = new ArrayList(); - for (ActionDefinition actionDefinition : getActionDefinitions()) - { - List appliciableTypes = actionDefinition.getApplicableTypes(); - if (appliciableTypes != null && appliciableTypes.isEmpty() == false) - { - for (QName applicableType : actionDefinition.getApplicableTypes()) - { - if (this.dictionaryService.isSubClass(nodeType, applicableType)) - { - result.add(actionDefinition); - break; - } - } - } - else - { - result.add(actionDefinition); - } - } - - return result; - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinition(java.lang.String) - */ - public ActionConditionDefinition getActionConditionDefinition(String name) - { - return this.conditionDefinitions.get(name); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinitions() - */ - public List getActionConditionDefinitions() - { - return new ArrayList(this.conditionDefinitions.values()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String) - */ - public ActionCondition createActionCondition(String name) - { - if (logger.isDebugEnabled()) - logger.debug("Creating Action Condition - [" + name + "]"); - - if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) - { - return new CompositeActionConditionImpl(GUID.generate()); - } - - return new ActionConditionImpl(GUID.generate(), name); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, java.util.Map) - */ - public ActionCondition createActionCondition(String name, Map params) - { - ActionCondition condition = createActionCondition(name); - condition.setParameterValues(params); - return condition; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createAction() - */ - public Action createAction(String name) - { - return new ActionImpl(null, GUID.generate(),name, null); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, java.util.Map) - */ - public Action createAction(String name, Map params) - { - Action action = createAction(name); - action.setParameterValues(params); - return action; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createCompositeAction() - */ - public CompositeAction createCompositeAction() - { - return new CompositeActionImpl(null, GUID.generate()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createCompositeActionCondition() - */ - public CompositeActionCondition createCompositeActionCondition() - { - return new CompositeActionConditionImpl(GUID.generate()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateAction(Action action, NodeRef actionedUponNodeRef) - { - boolean result = true; - - if (action.hasActionConditions() == true) - { - List actionConditions = action.getActionConditions(); - for (ActionCondition condition : actionConditions) - { - boolean tempresult = evaluateActionCondition(condition, actionedUponNodeRef); - - if (logger.isDebugEnabled()) - logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + tempresult); - - result = result && tempresult; - } - } - - if (logger.isDebugEnabled()) - logger.debug("\tAll Condition Evaluation Result - " + result); - - return result; - } - - /** - * Evaluates the actions by finding corresponding actionEvaluators in applicationContext (registered through Spring). - * Composite conditions are evaluated here as well. It is also possible to have composite actions inside composite actions. - * - * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateActionCondition(ActionCondition condition, NodeRef actionedUponNodeRef) - { - if (condition instanceof CompositeActionCondition) - { - CompositeActionCondition compositeCondition = (CompositeActionCondition) condition; - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + (compositeCondition.isORCondition()?"OR":"AND")); - } - - if (!compositeCondition.hasActionConditions()) - { - throw new IllegalStateException("CompositeActionCondition has no subconditions."); - } - - boolean result ; - if (compositeCondition.isORCondition()) - { - result = false; - } - else - { - result = true; - } - - for (ActionCondition simplecondition : compositeCondition.getActionConditions()) - { - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); - } - - if (compositeCondition.isORCondition()) - { - result = result || evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - - //Short circuit for the OR condition - if (result) break; - } - else - { - result = result && evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - //Short circuit for the AND condition - if (!result) break; - } - } - - if (compositeCondition.getInvertCondition()) - { - return !result; - } - else - { - return result; - } - } - else - { - return evaluateSimpleCondition(condition, actionedUponNodeRef); - } - } - - private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) - { - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); - } - // Evaluate the condition - ActionConditionEvaluator evaluator = (ActionConditionEvaluator)this.applicationContext.getBean(condition.getActionConditionDefinitionName()); - return evaluator.evaluate(condition, actionedUponNodeRef); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions) - { - executeAction(action, actionedUponNodeRef, checkConditions, action.getExecuteAsychronously()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously) - { - Set actionChain = this.currentActionChain.get(); - - if (executeAsychronously == false) - { - executeActionImpl(action, actionedUponNodeRef, checkConditions, false, actionChain); - } - else - { - // Add to the post transaction pending action list - addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain); - } - } - - /** - * called by transaction service. - */ - public void postCommit() - { - for (PendingAction pendingAction : getPostTransactionPendingActions()) - { - queueAction(pendingAction); - } - } - - /** - * - */ - private void queueAction(PendingAction action) - { - // Get the right queue - AsynchronousActionExecutionQueue queue = getQueue(action.action); - - // Queue the action for execution - queue.executeAction( - this, - action.getAction(), - action.getActionedUponNodeRef(), - action.getCheckConditions(), - action.getActionChain()); - } - - /** - * - * @param compensatingAction - * @param actionedUponNodeRef - */ - private void queueAction(Action compensatingAction, NodeRef actionedUponNodeRef) - { - // Get the right queue - AsynchronousActionExecutionQueue queue = getQueue(compensatingAction); - - // Queue the action for execution - queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); - } - - private AsynchronousActionExecutionQueue getQueue(Action action) - { - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); - AsynchronousActionExecutionQueue queue = null; - - String queueName = executer.getQueueName(); - if(queueName == null) - { - queue = asynchronousActionExecutionQueues.get(""); - } - else - { - queue = asynchronousActionExecutionQueues.get(queueName); - } - if(queue == null) - { - // can't get queue - throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: "+ queueName); - } - - return queue; - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean, org.alfresco.service.cmr.repository.NodeRef) - */ - public void executeActionImpl( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - boolean executedAsynchronously, - Set actionChain) - { - if (logger.isDebugEnabled() == true) - { - StringBuilder builder = new StringBuilder("Execute action impl action chain = "); - if (actionChain == null) - { - builder.append("null"); - } - else - { - for (String value : actionChain) - { - builder.append(value).append(" "); - } - } - logger.debug(builder.toString()); - logger.debug("Current action = " + action.getId()); - } - - // get the current user early in case the process fails and we are unable to do it later - String currentUserName = this.authenticationComponent.getCurrentUserName(); - - if (actionChain == null || actionChain.contains(action.getId()) == false) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Doing executeActionImpl"); - } - - try - { - Set origActionChain = null; - - if (actionChain == null) - { - actionChain = new HashSet(); - } - else - { - origActionChain = new HashSet(actionChain); - } - actionChain.add(action.getId()); - this.currentActionChain.set(actionChain); - - if (logger.isDebugEnabled() == true) - { - logger.debug("Adding " + action.getId() + " to action chain."); - } - - try - { - // Check and execute now - if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) - { - // Execute the action - directActionExecution(action, actionedUponNodeRef); - } - } - finally - { - if (origActionChain == null) - { - this.currentActionChain.remove(); - } - else - { - this.currentActionChain.set(origActionChain); - } - - if (logger.isDebugEnabled() == true) - { - logger.debug("Resetting the action chain."); - } - } - } - catch (Throwable exception) - { - // DH: No logging of the exception. Leave the logging decision to the client code, - // which can handle the rethrown exception. - if (executedAsynchronously == true) - { - // If one is specified, queue the compensating action ready for execution - Action compensatingAction = action.getCompensatingAction(); - if (compensatingAction != null) - { - // Set the current user - ((ActionImpl)compensatingAction).setRunAsUser(currentUserName); - queueAction(compensatingAction, actionedUponNodeRef); - } - } - - // Rethrow the exception - if (exception instanceof RuntimeException) - { - throw (RuntimeException)exception; - } - else - { - throw new ActionServiceException(ERR_FAIL, exception); - } - - } - } - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - public void directActionExecution(Action action, NodeRef actionedUponNodeRef) - { - // Debug output - if (logger.isDebugEnabled() == true) - { - logger.debug("The action is being executed as the user: " + this.authenticationComponent.getCurrentUserName()); - } - - // Get the action executer and execute - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); - executer.execute(action, actionedUponNodeRef); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, NodeRef) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef) - { - executeAction(action, actionedUponNodeRef, true); - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#registerActionConditionEvaluator(org.alfresco.repo.action.evaluator.ActionConditionEvaluator) - */ - public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) - { - ActionConditionDefinition cond = actionConditionEvaluator.getActionConditionDefintion(); - this.conditionDefinitions.put(cond.getName(), cond); - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#registerActionExecuter(org.alfresco.repo.action.executer.ActionExecuter) - */ - public void registerActionExecuter(ActionExecuter actionExecuter) - { - ActionDefinition action = actionExecuter.getActionDefinition(); - this.actionDefinitions.put(action.getName(), action); - } - - /** - * Gets the action node ref from the action id - * - * @param nodeRef the node reference - * @param actionId the action id - * @return the action node reference - */ - private NodeRef getActionNodeRefFromId(NodeRef nodeRef, String actionId) - { - NodeRef result = null; - - if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); - namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); - - List nodeRefs = searchService.selectNodes( - getSavedActionFolderRef(nodeRef), - "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", - null, - namespacePrefixResolver, - false); - if (nodeRefs.size() != 0) - { - result = nodeRefs.get(0); - } - } - - return result; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void saveAction(NodeRef nodeRef, Action action) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); - if (actionNodeRef == null) - { - if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == false) - { - // Apply the actionable aspect - this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); - } - - // Create the action and reference - actionNodeRef = createActionNodeRef(action, - getSavedActionFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - ActionModel.ASSOC_NAME_ACTIONS); - } - saveActionImpl(actionNodeRef, action); - } - - public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) - { - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - QName actionType = ActionModel.TYPE_ACTION; - if(action instanceof CompositeAction) - { - actionType = ActionModel.TYPE_COMPOSITE_ACTION; - } - - // Create the action node - NodeRef actionNodeRef = this.nodeService.createNode( - parentNodeRef, - assocTypeName, - assocName, - actionType, - props).getChildRef(); - - // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - - return actionNodeRef; - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void saveActionImpl(NodeRef actionNodeRef, Action action) - { - // Save action properties - saveActionProperties(actionNodeRef, action); - - // Update the parameters of the action - saveParameters(actionNodeRef, action); - - // Update the conditions of the action - saveConditions(actionNodeRef, action); - - if (action instanceof CompositeAction) - { - // Update composite action - saveCompositeActions(actionNodeRef, (CompositeAction)action); - } - - // Update the modified details - ((ActionImpl)action).setModifier((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIED)); - } - - /** - * Save the action property values - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void saveActionProperties(NodeRef actionNodeRef, Action action) - { - // Update the action property values - Map props = this.nodeService.getProperties(actionNodeRef); - props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); - props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); - props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); - this.nodeService.setProperties(actionNodeRef, props); - - // Update the compensating action (model should enforce the singularity of this association) - Action compensatingAction = action.getCompensatingAction(); - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); - if (assocs.size() == 0) - { - if (compensatingAction != null) - { - //Map props2 = new HashMap(2); - //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); - //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); - - //NodeRef compensatingActionNodeRef = this.nodeService.createNode( - /// actionNodeRef, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.TYPE_ACTION, - // props2).getChildRef(); - - // Create the compensating node reference - NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); - saveActionImpl(compensatingActionNodeRef, compensatingAction); - } - } - else - { - ChildAssociationRef assoc = assocs.get(0); - if (compensatingAction == null) - { - this.nodeService.removeChild(actionNodeRef, assoc.getChildRef()); - } - else - { - saveActionImpl(assoc.getChildRef(), compensatingAction); - } - } - } - - /** - * Save the actions of a composite action - * - * @param compositeActionNodeRef the node reference of the composite action - * @param compositeAction the composite action - */ - private void saveCompositeActions(NodeRef compositeActionNodeRef, CompositeAction compositeAction) - { - // TODO Need a way of sorting the order of the actions - - Map idToAction = new HashMap(); - List orderedIds = new ArrayList(); - for (Action action : compositeAction.getActions()) - { - idToAction.put(action.getId(), action); - orderedIds.add(action.getId()); - } - - List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); - for (ChildAssociationRef actionRef : actionRefs) - { - NodeRef actionNodeRef = actionRef.getChildRef(); - if (idToAction.containsKey(actionNodeRef.getId()) == false) - { - // Delete the action - this.nodeService.removeChild(compositeActionNodeRef, actionNodeRef); - } - else - { - // Update the action - Action action = idToAction.get(actionNodeRef.getId()); - saveActionImpl(actionNodeRef, action); - orderedIds.remove(actionNodeRef.getId()); - } - - } - - // Create the actions remaining - for (String actionId : orderedIds) - { - Action action = idToAction.get(actionId); - - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - NodeRef actionNodeRef = this.nodeService.createNode( - compositeActionNodeRef, - ActionModel.ASSOC_ACTIONS, - ActionModel.ASSOC_ACTIONS, - ActionModel.TYPE_ACTION, - props).getChildRef(); - - // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - - saveActionImpl(actionNodeRef, action); - } - } - - /** - * Saves the conditions associated with an action. - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void saveConditions(NodeRef actionNodeRef, Action action) - { - // TODO Need a way of sorting out the order of the conditions - List actionConditionsList = action.getActionConditions(); - saveActionConditionList(actionNodeRef, actionConditionsList, false); - } - - private void saveActionConditionList(NodeRef actionNodeRef, - List actionConditionsList, boolean isComposite) - { - if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list, "+ actionConditionsList.size() + - (isComposite?" Composite":"") + " conditions to be saved"); - - Map idToCondition = new HashMap(); - List orderedIds = new ArrayList(); - - for (ActionCondition actionCondition : actionConditionsList) - { - idToCondition.put(actionCondition.getId(), actionCondition); - orderedIds.add(actionCondition.getId()); - } - - List conditionRefs = this.nodeService.getChildAssocs( - actionNodeRef, RegexQNamePattern.MATCH_ALL, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); - - for (ChildAssociationRef conditionRef : conditionRefs) - { - NodeRef conditionNodeRef = conditionRef.getChildRef(); - if (idToCondition.containsKey(conditionNodeRef.getId()) == false) - { - // Delete the condition - this.nodeService.removeChild(actionNodeRef, conditionNodeRef); - } - else - { - saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - // Update the conditions parameters - saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - orderedIds.remove(conditionNodeRef.getId()); - } - } - - // Create the conditions remaining - for (String nextId : orderedIds) - { - ActionCondition actionCondition = idToCondition.get(nextId); - - if (!isComposite && actionCondition instanceof CompositeActionCondition) - { - if (logger.isDebugEnabled()) - logger.debug("Saving Composite Condition"); - - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); - saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition).getActionConditions(), true); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); - - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, - ActionModel.TYPE_ACTION_CONDITION); - } - } - } - - /* - private void saveCompositeActionConditionList(NodeRef compositeConditionRef, - List actionConditionsList) - { - if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list Composite, "+ actionConditionsList.size() + " conditions to be saved"); - - Map idToCondition = new HashMap(); - List orderedIds = new ArrayList(); - - for (ActionCondition actionCondition : actionConditionsList) - { - idToCondition.put(actionCondition.getId(), actionCondition); - orderedIds.add(actionCondition.getId()); - } - - List conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - for (ChildAssociationRef conditionRef : conditionRefs) - { - NodeRef conditionNodeRef = conditionRef.getChildRef(); - if (idToCondition.containsKey(conditionNodeRef.getId()) == false) - { - // Delete the condition - this.nodeService.removeChild(compositeConditionRef, conditionNodeRef); - } - else - { - saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - // Update the conditions parameters - saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - orderedIds.remove(conditionNodeRef.getId()); - } - } - - // Create the conditions remaining - for (String nextId : orderedIds) - { - ActionCondition actionCondition = idToCondition.get(nextId); - NodeRef conditionNodeRef = saveActionCondition(compositeConditionRef, actionCondition, ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); - - } - }*/ - - - private NodeRef saveActionCondition(NodeRef actionNodeRef, - ActionCondition actionCondition, QName AssociationQName, QName typeName) - { - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, actionCondition.getActionConditionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, actionCondition.getId()); - - NodeRef conditionNodeRef = this.nodeService.createNode( - actionNodeRef, - AssociationQName, - AssociationQName, - typeName, - props).getChildRef(); - - saveConditionProperties(conditionNodeRef, actionCondition); - saveParameters(conditionNodeRef, actionCondition); - return conditionNodeRef; - } - - /** - * Save the condition properties - * - * @param conditionNodeRef - * @param condition - */ - private void saveConditionProperties(NodeRef conditionNodeRef, ActionCondition condition) - { - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition.getInvertCondition()); - - if (condition instanceof CompositeActionCondition) - { - if (logger.isDebugEnabled()) - { - logger.debug("SAVING OR = " + ((CompositeActionCondition)condition).isORCondition()); - } - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean(((CompositeActionCondition)condition).isORCondition())); - } - } - - /** - * Saves the parameters associated with an action or condition - * - * @param parameterizedNodeRef the parameterized item node reference - * @param item the parameterized item - */ - private void saveParameters(NodeRef parameterizedNodeRef, ParameterizedItem item) - { - Map parameterMap = new HashMap(); - parameterMap.putAll(item.getParameterValues()); - - List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); - for (ChildAssociationRef ref : parameters) - { - NodeRef paramNodeRef = ref.getChildRef(); - Map nodeRefParameterMap = this.nodeService.getProperties(paramNodeRef); - String paramName = (String)nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); - if (parameterMap.containsKey(paramName) == false) - { - // Delete parameter from node ref - this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); - } - else - { - // Update the parameter value - nodeRefParameterMap.put(ActionModel.PROP_PARAMETER_VALUE, parameterMap.get(paramName)); - this.nodeService.setProperties(paramNodeRef, nodeRefParameterMap); - parameterMap.remove(paramName); - } - } - - // Add any remaining parameters - for (Map.Entry entry : parameterMap.entrySet()) - { - Map nodeRefProperties = new HashMap(2); - nodeRefProperties.put(ActionModel.PROP_PARAMETER_NAME, entry.getKey()); - nodeRefProperties.put(ActionModel.PROP_PARAMETER_VALUE, entry.getValue()); - - this.nodeService.createNode( - parameterizedNodeRef, - ActionModel.ASSOC_PARAMETERS, - ActionModel.ASSOC_PARAMETERS, - ActionModel.TYPE_ACTION_PARAMETER, - nodeRefProperties); - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActions(org.alfresco.service.cmr.repository.NodeRef) - */ - public List getActions(NodeRef nodeRef) - { - List result = new ArrayList(); - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - List actions = this.nodeService.getChildAssocs( - getSavedActionFolderRef(nodeRef), - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); - for (ChildAssociationRef action : actions) - { - NodeRef actionNodeRef = action.getChildRef(); - result.add(createAction(actionNodeRef)); - } - } - - return result; - } - - /** - * Create an action from the action node reference - * - * @param actionNodeRef the action node reference - * @return the action - */ - public Action createAction(NodeRef actionNodeRef) - { - Action result = null; - - Map properties = this.nodeService.getProperties(actionNodeRef); - - QName actionType = this.nodeService.getType(actionNodeRef); - if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) - { - // Create a composite action - result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); - populateCompositeAction(actionNodeRef, (CompositeAction)result); - } - else - { - // Create an action - result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); - populateAction(actionNodeRef, result); - } - - return result; - } - - /** - * Populate the details of the action from the node reference - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void populateAction(NodeRef actionNodeRef, Action action) - { - // Populate the action properties - populateActionProperties(actionNodeRef, action); - - // Set the parameters - populateParameters(actionNodeRef, action); - - // Set the conditions - List conditions = this.nodeService.getChildAssocs(actionNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - - if (logger.isDebugEnabled()) - logger.debug("Retrieving " + (conditions==null ? " null" : conditions.size()) + " conditions"); - - for (ChildAssociationRef condition : conditions) - { - NodeRef conditionNodeRef = condition.getChildRef(); - action.addActionCondition(createActionCondition(conditionNodeRef)); - } - } - - /** - * Populates the action properties from the node reference - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void populateActionProperties(NodeRef actionNodeRef, Action action) - { - Map props = this.nodeService.getProperties(actionNodeRef); - - action.setTitle((String)props.get(ActionModel.PROP_ACTION_TITLE)); - action.setDescription((String)props.get(ActionModel.PROP_ACTION_DESCRIPTION)); - - boolean value = false; - Boolean executeAsynchronously = (Boolean)props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); - if (executeAsynchronously != null) - { - value = executeAsynchronously.booleanValue(); - } - action.setExecuteAsynchronously(value); - - ((ActionImpl)action).setCreator((String)props.get(ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)props.get(ContentModel.PROP_CREATED)); - ((ActionImpl)action).setModifier((String)props.get(ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)props.get(ContentModel.PROP_MODIFIED)); - - // Get the compensating action - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); - if (assocs.size() != 0) - { - Action compensatingAction = createAction(assocs.get(0).getChildRef()); - action.setCompensatingAction(compensatingAction); - } - } - - /** - * Populate the parameters of a parameterized item from the parameterized item node reference - * - * @param parameterizedItemNodeRef the parameterized item node reference - * @param parameterizedItem the parameterized item - */ - private void populateParameters(NodeRef parameterizedItemNodeRef, ParameterizedItem parameterizedItem) - { - List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); - for (ChildAssociationRef parameter : parameters) - { - NodeRef parameterNodeRef = parameter.getChildRef(); - Map properties = this.nodeService.getProperties(parameterNodeRef); - parameterizedItem.setParameterValue( - (String)properties.get(ActionModel.PROP_PARAMETER_NAME), - properties.get(ActionModel.PROP_PARAMETER_VALUE)); - } - } - - /** - * Creates an action condition from an action condition node reference - * - * @param conditionNodeRef the condition node reference - * @return the action condition - */ - private ActionCondition createActionCondition(NodeRef conditionNodeRef) - { - if (logger.isDebugEnabled()) - logger.debug("\tCreateActionCondition: Retrieving Conditions from repository"); - - Map properties = this.nodeService.getProperties(conditionNodeRef); - QName conditionType = this.nodeService.getType(conditionNodeRef); - - ActionCondition condition = null; - if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) - { - condition = new ActionConditionImpl(conditionNodeRef.getId(), - (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("\tRetrieving Composite Condition from repository"); - } - - // Create a composite condition - CompositeActionCondition compositeCondition = new CompositeActionConditionImpl(GUID.generate()); - populateCompositeActionCondition(conditionNodeRef, compositeCondition); - - condition = compositeCondition; - } - - Boolean invert = (Boolean)this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); - condition.setInvertCondition(invert == null? false: invert.booleanValue()); - - populateParameters(conditionNodeRef, condition); - return condition; - } - - private void populateCompositeActionCondition(NodeRef compositeNodeRef, CompositeActionCondition compositeActionCondition) - { - List conditions = this.nodeService.getChildAssocs(compositeNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); - - Boolean OR = (Boolean) this.nodeService.getProperty(compositeNodeRef, ActionModel.PROP_CONDITION_ANDOR); - - if (logger.isDebugEnabled()) - { - logger.debug("\tPopulating Composite Condition with subconditions, Condition OR = " + OR); - } - - compositeActionCondition.setORCondition(OR == null? false: OR.booleanValue()); - - for (ChildAssociationRef conditionNodeRef : conditions) - { - NodeRef actionNodeRef = conditionNodeRef.getChildRef(); - ActionCondition currentCondition = createActionCondition(actionNodeRef); - - if (logger.isDebugEnabled()) - logger.debug("\t\tAdding subcondition " + currentCondition.getActionConditionDefinitionName()); - - compositeActionCondition.addActionCondition(currentCondition); - } - } - - - /** - * Populates a composite action from a composite action node reference - * - * @param compositeNodeRef the composite action node reference - * @param compositeAction the composite action - */ - public void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction) - { - populateAction(compositeNodeRef, compositeAction); - - List actions = this.nodeService.getChildAssocs(compositeNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); - for (ChildAssociationRef action : actions) - { - NodeRef actionNodeRef = action.getChildRef(); - compositeAction.addAction(createAction(actionNodeRef)); - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public Action getAction(NodeRef nodeRef, String actionId) - { - Action result = null; - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); - if (actionNodeRef != null) - { - result = createAction(actionNodeRef); - } - } - - return result; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void removeAction(NodeRef nodeRef, Action action) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); - if (actionNodeRef != null) - { - this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), actionNodeRef); - } - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#removeAllActions(org.alfresco.service.cmr.repository.NodeRef) - */ - public void removeAllActions(NodeRef nodeRef) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - List actions = new ArrayList(this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); - for (ChildAssociationRef action : actions) - { - this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), action.getChildRef()); - } - } - } - - /** - * Add a pending action to the list to be queued for execution once the transaction is completed. - * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicates whether to check the conditions before execution - */ - @SuppressWarnings("unchecked") - private void addPostTransactionPendingAction( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - Set actionChain) - { - if (logger.isDebugEnabled() == true) - { - StringBuilder builder = new StringBuilder("addPostTransactionPendingAction action chain = "); - if (actionChain == null) - { - builder.append("null"); - } - else - { - for (String value : actionChain) - { - builder.append(value).append(" "); - } - } - logger.debug(builder.toString()); - logger.debug("Current action = " + action.getId()); - } - - // Don't continue if the action is already in the action chain - if (actionChain == null || actionChain.contains(action.getId()) == false) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Doing addPostTransactionPendingAction"); - } - - // Set the run as user to the current user - if (logger.isDebugEnabled() == true) - { - logger.debug("The current user is: " + this.authenticationComponent.getCurrentUserName()); - } - ((ActionImpl)action).setRunAsUser(this.authenticationComponent.getCurrentUserName()); - - // Ensure that the transaction listener is bound to the transaction - AlfrescoTransactionSupport.bindListener(this.transactionListener); - - // Add the pending action to the transaction resource - List pendingActions = (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); - if (pendingActions == null) - { - pendingActions = new ArrayList(); - AlfrescoTransactionSupport.bindResource(POST_TRANSACTION_PENDING_ACTIONS, pendingActions); - } - - // Check that action has only been added to the list once - PendingAction pendingAction = new PendingAction(action, actionedUponNodeRef, checkConditions, actionChain); - if (pendingActions.contains(pendingAction) == false) - { - pendingActions.add(pendingAction); - } - } - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#getPostTransactionPendingActions() - */ - @SuppressWarnings("unchecked") - private List getPostTransactionPendingActions() - { - return (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); - } - - /** - * Pending action details class - */ - private class PendingAction - { - /** - * The action - */ - private Action action; - - /** - * The actioned upon node reference - */ - private NodeRef actionedUponNodeRef; - - /** - * Indicates whether the conditions should be checked before the action is executed - */ - private boolean checkConditions; - - private Set actionChain; - - /** - * Constructor - * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicated whether the conditions need to be checked - */ - public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain) - { - this.action = action; - this.actionedUponNodeRef = actionedUponNodeRef; - this.checkConditions = checkConditions; - this.actionChain = actionChain; - } - - /** - * Get the action - * - * @return the action - */ - public Action getAction() - { - return action; - } - - /** - * Get the actioned upon node reference - * - * @return the actioned upon node reference - */ - public NodeRef getActionedUponNodeRef() - { - return actionedUponNodeRef; - } - - /** - * Get the check conditions value - * - * @return indicates whether the condition should be checked - */ - public boolean getCheckConditions() - { - return this.checkConditions; - } - - public Set getActionChain() - { - return this.actionChain; - } - - /** - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() - { - int hashCode = 37 * this.actionedUponNodeRef.hashCode(); - hashCode += 37 * this.action.hashCode(); - return hashCode; - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj instanceof PendingAction) - { - PendingAction that = (PendingAction) obj; - return (this.action.equals(that.action) && this.actionedUponNodeRef.equals(that.actionedUponNodeRef)); - } - else - { - return false; - } - } - } -} +/* + * 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.repo.action; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.ActionConditionEvaluator; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionConditionDefinition; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.action.CompositeActionCondition; +import org.alfresco.service.cmr.action.ParameterizedItem; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Action service implementation + * + * @author Roy Wetherall + */ +public class ActionServiceImpl implements ActionService, RuntimeActionService, ApplicationContextAware +{ + /** + * Transaction resource name + */ + private static final String POST_TRANSACTION_PENDING_ACTIONS = "postTransactionPendingActions"; + + /** + * Error message + */ + private static final String ERR_FAIL = "The action failed to execute due to an error."; + + /** + * The logger + */ + private static Log logger = LogFactory.getLog(ActionServiceImpl.class); + + /** + * Thread local containing the current action chain + */ + ThreadLocal> currentActionChain = new ThreadLocal>(); + + /** + * The application context + */ + private ApplicationContext applicationContext; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * The search service + */ + private SearchService searchService; + + /** The dictionary service */ + private DictionaryService dictionaryService; + + /** The authentication component */ + private AuthenticationContext authenticationContext; + + /** + * The asynchronous action execution queues + * map of name, queue + */ + private Map asynchronousActionExecutionQueues; + + /** + * Action transaction listener + */ + private ActionTransactionListener transactionListener = new ActionTransactionListener(this); + + /** + * All the condition definitions currently registered + */ + private Map conditionDefinitions = new HashMap(); + + /** + * All the action definitions currently registered + */ + private Map actionDefinitions = new HashMap(); + + /** + * Set the application context + * + * @param applicationContext the application context + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the search service + * + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Set the authentication component + * + * @param authenticationContext the authentication component + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the asynchronous action execution queues + * + * @param asynchronousActionExecutionQueue the asynchronous action execution queues + */ + public void setAsynchronousActionExecutionQueues( + Map asynchronousActionExecutionQueues) + { + this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; + } + +// /** +// * Get the asynchronous action execution queue +// * +// * @return the asynchronous action execution queue +// */ +// public AsynchronousActionExecutionQueue getAsynchronousActionExecutionQueue() +// { +// return asynchronousActionExecutionQueue; +// } +// + /** + * Gets the saved action folder reference + * + * @param nodeRef the node reference + * @return the node reference + */ + private NodeRef getSavedActionFolderRef(NodeRef nodeRef) + { + List assocs = this.nodeService.getChildAssocs( + nodeRef, + RegexQNamePattern.MATCH_ALL, + ActionModel.ASSOC_ACTION_FOLDER); + if (assocs.size() != 1) + { + throw new ActionServiceException("Unable to retrieve the saved action folder reference."); + } + + return assocs.get(0).getChildRef(); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinition(java.lang.String) + */ + public ActionDefinition getActionDefinition(String name) + { + // get direct access to action definition (i.e. ignoring public flag of executer) + ActionDefinition definition = null; + Object bean = this.applicationContext.getBean(name); + if (bean != null && bean instanceof ActionExecuter) + { + ActionExecuter executer = (ActionExecuter)bean; + definition = executer.getActionDefinition(); + } + return definition; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions() + */ + public List getActionDefinitions() + { + return new ArrayList(this.actionDefinitions.values()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getActionDefinitions(NodeRef nodeRef) + { + if (nodeRef == null) + { + return getActionDefinitions(); + } + else + { + // TODO for now we will only filter by type, we will introduce filtering by aspect later + QName nodeType = this.nodeService.getType(nodeRef); + List result = new ArrayList(); + for (ActionDefinition actionDefinition : getActionDefinitions()) + { + List appliciableTypes = actionDefinition.getApplicableTypes(); + if (appliciableTypes != null && appliciableTypes.isEmpty() == false) + { + for (QName applicableType : actionDefinition.getApplicableTypes()) + { + if (this.dictionaryService.isSubClass(nodeType, applicableType)) + { + result.add(actionDefinition); + break; + } + } + } + else + { + result.add(actionDefinition); + } + } + + return result; + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinition(java.lang.String) + */ + public ActionConditionDefinition getActionConditionDefinition(String name) + { + return this.conditionDefinitions.get(name); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinitions() + */ + public List getActionConditionDefinitions() + { + return new ArrayList(this.conditionDefinitions.values()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String) + */ + public ActionCondition createActionCondition(String name) + { + if (logger.isDebugEnabled()) + logger.debug("Creating Action Condition - [" + name + "]"); + + if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) + { + return new CompositeActionConditionImpl(GUID.generate()); + } + + return new ActionConditionImpl(GUID.generate(), name); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, java.util.Map) + */ + public ActionCondition createActionCondition(String name, Map params) + { + ActionCondition condition = createActionCondition(name); + condition.setParameterValues(params); + return condition; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createAction() + */ + public Action createAction(String name) + { + return new ActionImpl(null, GUID.generate(),name, null); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, java.util.Map) + */ + public Action createAction(String name, Map params) + { + Action action = createAction(name); + action.setParameterValues(params); + return action; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createCompositeAction() + */ + public CompositeAction createCompositeAction() + { + return new CompositeActionImpl(null, GUID.generate()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createCompositeActionCondition() + */ + public CompositeActionCondition createCompositeActionCondition() + { + return new CompositeActionConditionImpl(GUID.generate()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateAction(Action action, NodeRef actionedUponNodeRef) + { + boolean result = true; + + if (action.hasActionConditions() == true) + { + List actionConditions = action.getActionConditions(); + for (ActionCondition condition : actionConditions) + { + boolean tempresult = evaluateActionCondition(condition, actionedUponNodeRef); + + if (logger.isDebugEnabled()) + logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + tempresult); + + result = result && tempresult; + } + } + + if (logger.isDebugEnabled()) + logger.debug("\tAll Condition Evaluation Result - " + result); + + return result; + } + + /** + * Evaluates the actions by finding corresponding actionEvaluators in applicationContext (registered through Spring). + * Composite conditions are evaluated here as well. It is also possible to have composite actions inside composite actions. + * + * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateActionCondition(ActionCondition condition, NodeRef actionedUponNodeRef) + { + if (condition instanceof CompositeActionCondition) + { + CompositeActionCondition compositeCondition = (CompositeActionCondition) condition; + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + (compositeCondition.isORCondition()?"OR":"AND")); + } + + if (!compositeCondition.hasActionConditions()) + { + throw new IllegalStateException("CompositeActionCondition has no subconditions."); + } + + boolean result ; + if (compositeCondition.isORCondition()) + { + result = false; + } + else + { + result = true; + } + + for (ActionCondition simplecondition : compositeCondition.getActionConditions()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); + } + + if (compositeCondition.isORCondition()) + { + result = result || evaluateSimpleCondition(simplecondition, actionedUponNodeRef); + + //Short circuit for the OR condition + if (result) break; + } + else + { + result = result && evaluateSimpleCondition(simplecondition, actionedUponNodeRef); + //Short circuit for the AND condition + if (!result) break; + } + } + + if (compositeCondition.getInvertCondition()) + { + return !result; + } + else + { + return result; + } + } + else + { + return evaluateSimpleCondition(condition, actionedUponNodeRef); + } + } + + private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) + { + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); + } + // Evaluate the condition + ActionConditionEvaluator evaluator = (ActionConditionEvaluator)this.applicationContext.getBean(condition.getActionConditionDefinitionName()); + return evaluator.evaluate(condition, actionedUponNodeRef); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions) + { + executeAction(action, actionedUponNodeRef, checkConditions, action.getExecuteAsychronously()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously) + { + Set actionChain = this.currentActionChain.get(); + + if (executeAsychronously == false) + { + executeActionImpl(action, actionedUponNodeRef, checkConditions, false, actionChain); + } + else + { + // Add to the post transaction pending action list + addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain); + } + } + + /** + * called by transaction service. + */ + public void postCommit() + { + for (PendingAction pendingAction : getPostTransactionPendingActions()) + { + queueAction(pendingAction); + } + } + + /** + * + */ + private void queueAction(PendingAction action) + { + // Get the right queue + AsynchronousActionExecutionQueue queue = getQueue(action.action); + + // Queue the action for execution + queue.executeAction( + this, + action.getAction(), + action.getActionedUponNodeRef(), + action.getCheckConditions(), + action.getActionChain()); + } + + /** + * + * @param compensatingAction + * @param actionedUponNodeRef + */ + private void queueAction(Action compensatingAction, NodeRef actionedUponNodeRef) + { + // Get the right queue + AsynchronousActionExecutionQueue queue = getQueue(compensatingAction); + + // Queue the action for execution + queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); + } + + private AsynchronousActionExecutionQueue getQueue(Action action) + { + ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); + AsynchronousActionExecutionQueue queue = null; + + String queueName = executer.getQueueName(); + if(queueName == null) + { + queue = asynchronousActionExecutionQueues.get(""); + } + else + { + queue = asynchronousActionExecutionQueues.get(queueName); + } + if(queue == null) + { + // can't get queue + throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: "+ queueName); + } + + return queue; + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean, org.alfresco.service.cmr.repository.NodeRef) + */ + public void executeActionImpl( + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + boolean executedAsynchronously, + Set actionChain) + { + if (logger.isDebugEnabled() == true) + { + StringBuilder builder = new StringBuilder("Execute action impl action chain = "); + if (actionChain == null) + { + builder.append("null"); + } + else + { + for (String value : actionChain) + { + builder.append(value).append(" "); + } + } + logger.debug(builder.toString()); + logger.debug("Current action = " + action.getId()); + } + + // get the current user early in case the process fails and we are unable to do it later + String currentUserName = this.authenticationContext.getCurrentUserName(); + + if (actionChain == null || actionChain.contains(action.getId()) == false) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Doing executeActionImpl"); + } + + try + { + Set origActionChain = null; + + if (actionChain == null) + { + actionChain = new HashSet(); + } + else + { + origActionChain = new HashSet(actionChain); + } + actionChain.add(action.getId()); + this.currentActionChain.set(actionChain); + + if (logger.isDebugEnabled() == true) + { + logger.debug("Adding " + action.getId() + " to action chain."); + } + + try + { + // Check and execute now + if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) + { + // Execute the action + directActionExecution(action, actionedUponNodeRef); + } + } + finally + { + if (origActionChain == null) + { + this.currentActionChain.remove(); + } + else + { + this.currentActionChain.set(origActionChain); + } + + if (logger.isDebugEnabled() == true) + { + logger.debug("Resetting the action chain."); + } + } + } + catch (Throwable exception) + { + // DH: No logging of the exception. Leave the logging decision to the client code, + // which can handle the rethrown exception. + if (executedAsynchronously == true) + { + // If one is specified, queue the compensating action ready for execution + Action compensatingAction = action.getCompensatingAction(); + if (compensatingAction != null) + { + // Set the current user + ((ActionImpl)compensatingAction).setRunAsUser(currentUserName); + queueAction(compensatingAction, actionedUponNodeRef); + } + } + + // Rethrow the exception + if (exception instanceof RuntimeException) + { + throw (RuntimeException)exception; + } + else + { + throw new ActionServiceException(ERR_FAIL, exception); + } + + } + } + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + public void directActionExecution(Action action, NodeRef actionedUponNodeRef) + { + // Debug output + if (logger.isDebugEnabled() == true) + { + logger.debug("The action is being executed as the user: " + this.authenticationContext.getCurrentUserName()); + } + + // Get the action executer and execute + ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); + executer.execute(action, actionedUponNodeRef); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, NodeRef) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef) + { + executeAction(action, actionedUponNodeRef, true); + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#registerActionConditionEvaluator(org.alfresco.repo.action.evaluator.ActionConditionEvaluator) + */ + public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) + { + ActionConditionDefinition cond = actionConditionEvaluator.getActionConditionDefintion(); + this.conditionDefinitions.put(cond.getName(), cond); + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#registerActionExecuter(org.alfresco.repo.action.executer.ActionExecuter) + */ + public void registerActionExecuter(ActionExecuter actionExecuter) + { + ActionDefinition action = actionExecuter.getActionDefinition(); + this.actionDefinitions.put(action.getName(), action); + } + + /** + * Gets the action node ref from the action id + * + * @param nodeRef the node reference + * @param actionId the action id + * @return the action node reference + */ + private NodeRef getActionNodeRefFromId(NodeRef nodeRef, String actionId) + { + NodeRef result = null; + + if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); + namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); + + List nodeRefs = searchService.selectNodes( + getSavedActionFolderRef(nodeRef), + "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", + null, + namespacePrefixResolver, + false); + if (nodeRefs.size() != 0) + { + result = nodeRefs.get(0); + } + } + + return result; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void saveAction(NodeRef nodeRef, Action action) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); + if (actionNodeRef == null) + { + if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == false) + { + // Apply the actionable aspect + this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); + } + + // Create the action and reference + actionNodeRef = createActionNodeRef(action, + getSavedActionFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + ActionModel.ASSOC_NAME_ACTIONS); + } + saveActionImpl(actionNodeRef, action); + } + + public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) + { + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, action.getId()); + + QName actionType = ActionModel.TYPE_ACTION; + if(action instanceof CompositeAction) + { + actionType = ActionModel.TYPE_COMPOSITE_ACTION; + } + + // Create the action node + NodeRef actionNodeRef = this.nodeService.createNode( + parentNodeRef, + assocTypeName, + assocName, + actionType, + props).getChildRef(); + + // Update the created details and the node reference + ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + ((ActionImpl)action).setNodeRef(actionNodeRef); + + return actionNodeRef; + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void saveActionImpl(NodeRef actionNodeRef, Action action) + { + // Save action properties + saveActionProperties(actionNodeRef, action); + + // Update the parameters of the action + saveParameters(actionNodeRef, action); + + // Update the conditions of the action + saveConditions(actionNodeRef, action); + + if (action instanceof CompositeAction) + { + // Update composite action + saveCompositeActions(actionNodeRef, (CompositeAction)action); + } + + // Update the modified details + ((ActionImpl)action).setModifier((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIER)); + ((ActionImpl)action).setModifiedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIED)); + } + + /** + * Save the action property values + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void saveActionProperties(NodeRef actionNodeRef, Action action) + { + // Update the action property values + Map props = this.nodeService.getProperties(actionNodeRef); + props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); + props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); + props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); + this.nodeService.setProperties(actionNodeRef, props); + + // Update the compensating action (model should enforce the singularity of this association) + Action compensatingAction = action.getCompensatingAction(); + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + if (assocs.size() == 0) + { + if (compensatingAction != null) + { + //Map props2 = new HashMap(2); + //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); + //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); + + //NodeRef compensatingActionNodeRef = this.nodeService.createNode( + /// actionNodeRef, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.TYPE_ACTION, + // props2).getChildRef(); + + // Create the compensating node reference + NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); + saveActionImpl(compensatingActionNodeRef, compensatingAction); + } + } + else + { + ChildAssociationRef assoc = assocs.get(0); + if (compensatingAction == null) + { + this.nodeService.removeChild(actionNodeRef, assoc.getChildRef()); + } + else + { + saveActionImpl(assoc.getChildRef(), compensatingAction); + } + } + } + + /** + * Save the actions of a composite action + * + * @param compositeActionNodeRef the node reference of the composite action + * @param compositeAction the composite action + */ + private void saveCompositeActions(NodeRef compositeActionNodeRef, CompositeAction compositeAction) + { + // TODO Need a way of sorting the order of the actions + + Map idToAction = new HashMap(); + List orderedIds = new ArrayList(); + for (Action action : compositeAction.getActions()) + { + idToAction.put(action.getId(), action); + orderedIds.add(action.getId()); + } + + List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + for (ChildAssociationRef actionRef : actionRefs) + { + NodeRef actionNodeRef = actionRef.getChildRef(); + if (idToAction.containsKey(actionNodeRef.getId()) == false) + { + // Delete the action + this.nodeService.removeChild(compositeActionNodeRef, actionNodeRef); + } + else + { + // Update the action + Action action = idToAction.get(actionNodeRef.getId()); + saveActionImpl(actionNodeRef, action); + orderedIds.remove(actionNodeRef.getId()); + } + + } + + // Create the actions remaining + for (String actionId : orderedIds) + { + Action action = idToAction.get(actionId); + + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, action.getId()); + + NodeRef actionNodeRef = this.nodeService.createNode( + compositeActionNodeRef, + ActionModel.ASSOC_ACTIONS, + ActionModel.ASSOC_ACTIONS, + ActionModel.TYPE_ACTION, + props).getChildRef(); + + // Update the created details and the node reference + ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + ((ActionImpl)action).setNodeRef(actionNodeRef); + + saveActionImpl(actionNodeRef, action); + } + } + + /** + * Saves the conditions associated with an action. + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void saveConditions(NodeRef actionNodeRef, Action action) + { + // TODO Need a way of sorting out the order of the conditions + List actionConditionsList = action.getActionConditions(); + saveActionConditionList(actionNodeRef, actionConditionsList, false); + } + + private void saveActionConditionList(NodeRef actionNodeRef, + List actionConditionsList, boolean isComposite) + { + if (logger.isDebugEnabled()) + logger.debug("SaveActionCondition list, "+ actionConditionsList.size() + + (isComposite?" Composite":"") + " conditions to be saved"); + + Map idToCondition = new HashMap(); + List orderedIds = new ArrayList(); + + for (ActionCondition actionCondition : actionConditionsList) + { + idToCondition.put(actionCondition.getId(), actionCondition); + orderedIds.add(actionCondition.getId()); + } + + List conditionRefs = this.nodeService.getChildAssocs( + actionNodeRef, RegexQNamePattern.MATCH_ALL, + !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + + for (ChildAssociationRef conditionRef : conditionRefs) + { + NodeRef conditionNodeRef = conditionRef.getChildRef(); + if (idToCondition.containsKey(conditionNodeRef.getId()) == false) + { + // Delete the condition + this.nodeService.removeChild(actionNodeRef, conditionNodeRef); + } + else + { + saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + // Update the conditions parameters + saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + orderedIds.remove(conditionNodeRef.getId()); + } + } + + // Create the conditions remaining + for (String nextId : orderedIds) + { + ActionCondition actionCondition = idToCondition.get(nextId); + + if (!isComposite && actionCondition instanceof CompositeActionCondition) + { + if (logger.isDebugEnabled()) + logger.debug("Saving Composite Condition"); + + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); + saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition).getActionConditions(), true); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); + + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, + ActionModel.TYPE_ACTION_CONDITION); + } + } + } + + /* + private void saveCompositeActionConditionList(NodeRef compositeConditionRef, + List actionConditionsList) + { + if (logger.isDebugEnabled()) + logger.debug("SaveActionCondition list Composite, "+ actionConditionsList.size() + " conditions to be saved"); + + Map idToCondition = new HashMap(); + List orderedIds = new ArrayList(); + + for (ActionCondition actionCondition : actionConditionsList) + { + idToCondition.put(actionCondition.getId(), actionCondition); + orderedIds.add(actionCondition.getId()); + } + + List conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); + for (ChildAssociationRef conditionRef : conditionRefs) + { + NodeRef conditionNodeRef = conditionRef.getChildRef(); + if (idToCondition.containsKey(conditionNodeRef.getId()) == false) + { + // Delete the condition + this.nodeService.removeChild(compositeConditionRef, conditionNodeRef); + } + else + { + saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + // Update the conditions parameters + saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + orderedIds.remove(conditionNodeRef.getId()); + } + } + + // Create the conditions remaining + for (String nextId : orderedIds) + { + ActionCondition actionCondition = idToCondition.get(nextId); + NodeRef conditionNodeRef = saveActionCondition(compositeConditionRef, actionCondition, ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); + + } + }*/ + + + private NodeRef saveActionCondition(NodeRef actionNodeRef, + ActionCondition actionCondition, QName AssociationQName, QName typeName) + { + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, actionCondition.getActionConditionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, actionCondition.getId()); + + NodeRef conditionNodeRef = this.nodeService.createNode( + actionNodeRef, + AssociationQName, + AssociationQName, + typeName, + props).getChildRef(); + + saveConditionProperties(conditionNodeRef, actionCondition); + saveParameters(conditionNodeRef, actionCondition); + return conditionNodeRef; + } + + /** + * Save the condition properties + * + * @param conditionNodeRef + * @param condition + */ + private void saveConditionProperties(NodeRef conditionNodeRef, ActionCondition condition) + { + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition.getInvertCondition()); + + if (condition instanceof CompositeActionCondition) + { + if (logger.isDebugEnabled()) + { + logger.debug("SAVING OR = " + ((CompositeActionCondition)condition).isORCondition()); + } + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean(((CompositeActionCondition)condition).isORCondition())); + } + } + + /** + * Saves the parameters associated with an action or condition + * + * @param parameterizedNodeRef the parameterized item node reference + * @param item the parameterized item + */ + private void saveParameters(NodeRef parameterizedNodeRef, ParameterizedItem item) + { + Map parameterMap = new HashMap(); + parameterMap.putAll(item.getParameterValues()); + + List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); + for (ChildAssociationRef ref : parameters) + { + NodeRef paramNodeRef = ref.getChildRef(); + Map nodeRefParameterMap = this.nodeService.getProperties(paramNodeRef); + String paramName = (String)nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); + if (parameterMap.containsKey(paramName) == false) + { + // Delete parameter from node ref + this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); + } + else + { + // Update the parameter value + nodeRefParameterMap.put(ActionModel.PROP_PARAMETER_VALUE, parameterMap.get(paramName)); + this.nodeService.setProperties(paramNodeRef, nodeRefParameterMap); + parameterMap.remove(paramName); + } + } + + // Add any remaining parameters + for (Map.Entry entry : parameterMap.entrySet()) + { + Map nodeRefProperties = new HashMap(2); + nodeRefProperties.put(ActionModel.PROP_PARAMETER_NAME, entry.getKey()); + nodeRefProperties.put(ActionModel.PROP_PARAMETER_VALUE, entry.getValue()); + + this.nodeService.createNode( + parameterizedNodeRef, + ActionModel.ASSOC_PARAMETERS, + ActionModel.ASSOC_PARAMETERS, + ActionModel.TYPE_ACTION_PARAMETER, + nodeRefProperties); + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActions(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getActions(NodeRef nodeRef) + { + List result = new ArrayList(); + + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + List actions = this.nodeService.getChildAssocs( + getSavedActionFolderRef(nodeRef), + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); + for (ChildAssociationRef action : actions) + { + NodeRef actionNodeRef = action.getChildRef(); + result.add(createAction(actionNodeRef)); + } + } + + return result; + } + + /** + * Create an action from the action node reference + * + * @param actionNodeRef the action node reference + * @return the action + */ + public Action createAction(NodeRef actionNodeRef) + { + Action result = null; + + Map properties = this.nodeService.getProperties(actionNodeRef); + + QName actionType = this.nodeService.getType(actionNodeRef); + if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) + { + // Create a composite action + result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); + populateCompositeAction(actionNodeRef, (CompositeAction)result); + } + else + { + // Create an action + result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + populateAction(actionNodeRef, result); + } + + return result; + } + + /** + * Populate the details of the action from the node reference + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void populateAction(NodeRef actionNodeRef, Action action) + { + // Populate the action properties + populateActionProperties(actionNodeRef, action); + + // Set the parameters + populateParameters(actionNodeRef, action); + + // Set the conditions + List conditions = this.nodeService.getChildAssocs(actionNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); + + if (logger.isDebugEnabled()) + logger.debug("Retrieving " + (conditions==null ? " null" : conditions.size()) + " conditions"); + + for (ChildAssociationRef condition : conditions) + { + NodeRef conditionNodeRef = condition.getChildRef(); + action.addActionCondition(createActionCondition(conditionNodeRef)); + } + } + + /** + * Populates the action properties from the node reference + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void populateActionProperties(NodeRef actionNodeRef, Action action) + { + Map props = this.nodeService.getProperties(actionNodeRef); + + action.setTitle((String)props.get(ActionModel.PROP_ACTION_TITLE)); + action.setDescription((String)props.get(ActionModel.PROP_ACTION_DESCRIPTION)); + + boolean value = false; + Boolean executeAsynchronously = (Boolean)props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + if (executeAsynchronously != null) + { + value = executeAsynchronously.booleanValue(); + } + action.setExecuteAsynchronously(value); + + ((ActionImpl)action).setCreator((String)props.get(ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)props.get(ContentModel.PROP_CREATED)); + ((ActionImpl)action).setModifier((String)props.get(ContentModel.PROP_MODIFIER)); + ((ActionImpl)action).setModifiedDate((Date)props.get(ContentModel.PROP_MODIFIED)); + + // Get the compensating action + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + if (assocs.size() != 0) + { + Action compensatingAction = createAction(assocs.get(0).getChildRef()); + action.setCompensatingAction(compensatingAction); + } + } + + /** + * Populate the parameters of a parameterized item from the parameterized item node reference + * + * @param parameterizedItemNodeRef the parameterized item node reference + * @param parameterizedItem the parameterized item + */ + private void populateParameters(NodeRef parameterizedItemNodeRef, ParameterizedItem parameterizedItem) + { + List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); + for (ChildAssociationRef parameter : parameters) + { + NodeRef parameterNodeRef = parameter.getChildRef(); + Map properties = this.nodeService.getProperties(parameterNodeRef); + parameterizedItem.setParameterValue( + (String)properties.get(ActionModel.PROP_PARAMETER_NAME), + properties.get(ActionModel.PROP_PARAMETER_VALUE)); + } + } + + /** + * Creates an action condition from an action condition node reference + * + * @param conditionNodeRef the condition node reference + * @return the action condition + */ + private ActionCondition createActionCondition(NodeRef conditionNodeRef) + { + if (logger.isDebugEnabled()) + logger.debug("\tCreateActionCondition: Retrieving Conditions from repository"); + + Map properties = this.nodeService.getProperties(conditionNodeRef); + QName conditionType = this.nodeService.getType(conditionNodeRef); + + ActionCondition condition = null; + if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) + { + condition = new ActionConditionImpl(conditionNodeRef.getId(), + (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("\tRetrieving Composite Condition from repository"); + } + + // Create a composite condition + CompositeActionCondition compositeCondition = new CompositeActionConditionImpl(GUID.generate()); + populateCompositeActionCondition(conditionNodeRef, compositeCondition); + + condition = compositeCondition; + } + + Boolean invert = (Boolean)this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); + condition.setInvertCondition(invert == null? false: invert.booleanValue()); + + populateParameters(conditionNodeRef, condition); + return condition; + } + + private void populateCompositeActionCondition(NodeRef compositeNodeRef, CompositeActionCondition compositeActionCondition) + { + List conditions = this.nodeService.getChildAssocs(compositeNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + + Boolean OR = (Boolean) this.nodeService.getProperty(compositeNodeRef, ActionModel.PROP_CONDITION_ANDOR); + + if (logger.isDebugEnabled()) + { + logger.debug("\tPopulating Composite Condition with subconditions, Condition OR = " + OR); + } + + compositeActionCondition.setORCondition(OR == null? false: OR.booleanValue()); + + for (ChildAssociationRef conditionNodeRef : conditions) + { + NodeRef actionNodeRef = conditionNodeRef.getChildRef(); + ActionCondition currentCondition = createActionCondition(actionNodeRef); + + if (logger.isDebugEnabled()) + logger.debug("\t\tAdding subcondition " + currentCondition.getActionConditionDefinitionName()); + + compositeActionCondition.addActionCondition(currentCondition); + } + } + + + /** + * Populates a composite action from a composite action node reference + * + * @param compositeNodeRef the composite action node reference + * @param compositeAction the composite action + */ + public void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction) + { + populateAction(compositeNodeRef, compositeAction); + + List actions = this.nodeService.getChildAssocs(compositeNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + for (ChildAssociationRef action : actions) + { + NodeRef actionNodeRef = action.getChildRef(); + compositeAction.addAction(createAction(actionNodeRef)); + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public Action getAction(NodeRef nodeRef, String actionId) + { + Action result = null; + + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); + if (actionNodeRef != null) + { + result = createAction(actionNodeRef); + } + } + + return result; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void removeAction(NodeRef nodeRef, Action action) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); + if (actionNodeRef != null) + { + this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), actionNodeRef); + } + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#removeAllActions(org.alfresco.service.cmr.repository.NodeRef) + */ + public void removeAllActions(NodeRef nodeRef) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + List actions = new ArrayList(this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); + for (ChildAssociationRef action : actions) + { + this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), action.getChildRef()); + } + } + } + + /** + * Add a pending action to the list to be queued for execution once the transaction is completed. + * + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicates whether to check the conditions before execution + */ + @SuppressWarnings("unchecked") + private void addPostTransactionPendingAction( + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + Set actionChain) + { + if (logger.isDebugEnabled() == true) + { + StringBuilder builder = new StringBuilder("addPostTransactionPendingAction action chain = "); + if (actionChain == null) + { + builder.append("null"); + } + else + { + for (String value : actionChain) + { + builder.append(value).append(" "); + } + } + logger.debug(builder.toString()); + logger.debug("Current action = " + action.getId()); + } + + // Don't continue if the action is already in the action chain + if (actionChain == null || actionChain.contains(action.getId()) == false) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Doing addPostTransactionPendingAction"); + } + + // Set the run as user to the current user + if (logger.isDebugEnabled() == true) + { + logger.debug("The current user is: " + this.authenticationContext.getCurrentUserName()); + } + ((ActionImpl)action).setRunAsUser(this.authenticationContext.getCurrentUserName()); + + // Ensure that the transaction listener is bound to the transaction + AlfrescoTransactionSupport.bindListener(this.transactionListener); + + // Add the pending action to the transaction resource + List pendingActions = (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + if (pendingActions == null) + { + pendingActions = new ArrayList(); + AlfrescoTransactionSupport.bindResource(POST_TRANSACTION_PENDING_ACTIONS, pendingActions); + } + + // Check that action has only been added to the list once + PendingAction pendingAction = new PendingAction(action, actionedUponNodeRef, checkConditions, actionChain); + if (pendingActions.contains(pendingAction) == false) + { + pendingActions.add(pendingAction); + } + } + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#getPostTransactionPendingActions() + */ + @SuppressWarnings("unchecked") + private List getPostTransactionPendingActions() + { + return (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + } + + /** + * Pending action details class + */ + private class PendingAction + { + /** + * The action + */ + private Action action; + + /** + * The actioned upon node reference + */ + private NodeRef actionedUponNodeRef; + + /** + * Indicates whether the conditions should be checked before the action is executed + */ + private boolean checkConditions; + + private Set actionChain; + + /** + * Constructor + * + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicated whether the conditions need to be checked + */ + public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain) + { + this.action = action; + this.actionedUponNodeRef = actionedUponNodeRef; + this.checkConditions = checkConditions; + this.actionChain = actionChain; + } + + /** + * Get the action + * + * @return the action + */ + public Action getAction() + { + return action; + } + + /** + * Get the actioned upon node reference + * + * @return the actioned upon node reference + */ + public NodeRef getActionedUponNodeRef() + { + return actionedUponNodeRef; + } + + /** + * Get the check conditions value + * + * @return indicates whether the condition should be checked + */ + public boolean getCheckConditions() + { + return this.checkConditions; + } + + public Set getActionChain() + { + return this.actionChain; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() + { + int hashCode = 37 * this.actionedUponNodeRef.hashCode(); + hashCode += 37 * this.action.hashCode(); + return hashCode; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof PendingAction) + { + PendingAction that = (PendingAction) obj; + return (this.action.equals(that.action) && this.actionedUponNodeRef.equals(that.actionedUponNodeRef)); + } + else + { + return false; + } + } + } +} diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index 94ed2dd784..bfc085939f 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -34,7 +34,7 @@ import org.alfresco.repo.action.AsynchronousActionExecutionQueuePolicies.OnAsync import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.rule.RuleServiceImpl; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; @@ -62,7 +62,7 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE /** Services */ private ThreadPoolExecutor threadPoolExecutor; private TransactionService transactionService; - private AuthenticationComponent authenticationComponent; + private AuthenticationContext authenticationContext; private PolicyComponent policyComponent; private NodeService nodeService; @@ -109,11 +109,11 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE /** * Set the authentication component * - * @param authenticationComponent the authentication component + * @param authenticationContext the authentication component */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + public void setAuthenticationContext(AuthenticationContext authenticationContext) { - this.authenticationComponent = authenticationComponent; + this.authenticationContext = authenticationContext; } /** diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index 7798d9b1ba..30dddc0a5b 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -1,558 +1,558 @@ -/* - * 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.repo.admin.patch; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.node.integrity.IntegrityChecker; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.Tenant; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.admin.PatchException; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Base implementation of the patch. This class ensures that the patch is thread- and transaction-safe. - * - * @author Derek Hulley - */ -public abstract class AbstractPatch implements Patch -{ - /** - * I18N message when properties not set. - *
    - *
  • {0} = property name
  • - *
  • {1} = patch instance
  • - *
- */ - public static final String ERR_PROPERTY_NOT_SET = "patch.general.property_not_set"; - private static final String MSG_PROGRESS = "patch.progress"; - - private static final long RANGE_10 = 1000 * 60 * 90; - private static final long RANGE_5 = 1000 * 60 * 60 * 4; - private static final long RANGE_2 = 1000 * 60 * 90 * 10; - - private static Log logger = LogFactory.getLog(AbstractPatch.class); - private static Log progress_logger = LogFactory.getLog(PatchExecuter.class); - - private String id; - private int fixesFromSchema; - private int fixesToSchema; - private int targetSchema; - private String description; - /** a list of patches that this one depends on */ - private List dependsOn; - /** a list of patches that, if already present, mean that this one should be ignored */ - private List alternatives; - /** flag indicating if the patch was successfully applied */ - private boolean applied; - private boolean applyToTenants; - /** track completion * */ - int percentComplete = 0; - /** start time * */ - long startTime; - - /** the service to register ourselves with */ - private PatchService patchService; - /** used to ensure a unique transaction per execution */ - protected TransactionService transactionService; - protected NamespaceService namespaceService; - protected NodeService nodeService; - protected SearchService searchService; - protected AuthenticationComponent authenticationComponent; - protected TenantAdminService tenantAdminService; - - - public AbstractPatch() - { - this.fixesFromSchema = -1; - this.fixesToSchema = -1; - this.targetSchema = -1; - this.applied = false; - this.applyToTenants = true; // by default, apply to each tenant, if tenant service is enabled - this.dependsOn = Collections.emptyList(); - this.alternatives = Collections.emptyList(); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(256); - sb.append("Patch") - .append("[ id=").append(id) - .append(", description=").append(description) - .append(", fixesFromSchema=").append(fixesFromSchema) - .append(", fixesToSchema=").append(fixesToSchema) - .append(", targetSchema=").append(targetSchema) - .append("]"); - return sb.toString(); - } - - /** - * Set the service that this patch will register with for execution. - */ - public void setPatchService(PatchService patchService) - { - this.patchService = patchService; - } - - /** - * Set the transaction provider so that each execution can be performed within a transaction - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - /** - * This ensures that this bean gets registered with the appropriate {@link PatchService service}. - */ - public void init() - { - if (patchService == null) - { - throw new AlfrescoRuntimeException("Mandatory property not set: patchService"); - } - patchService.registerPatch(this); - } - - public String getId() - { - return id; - } - - /** - * @param id - * the unique ID of the patch. This dictates the order in which patches are applied. - */ - public void setId(String id) - { - this.id = id; - } - - public int getFixesFromSchema() - { - return fixesFromSchema; - } - - /** - * Set the smallest schema number that this patch may be applied to. - * - * @param version - * a schema number not smaller than 0 - */ - public void setFixesFromSchema(int version) - { - if (version < 0) - { - throw new IllegalArgumentException("The 'fixesFromSchema' property may not be less than 0"); - } - this.fixesFromSchema = version; - // auto-adjust the to version - if (fixesToSchema < fixesFromSchema) - { - setFixesToSchema(this.fixesFromSchema); - } - } - - public int getFixesToSchema() - { - return fixesToSchema; - } - - /** - * Set the largest schema version number that this patch may be applied to. - * - * @param version - * a schema version number not smaller than the {@link #setFixesFromSchema(int) from version} number. - */ - public void setFixesToSchema(int version) - { - if (version < fixesFromSchema) - { - throw new IllegalArgumentException("'fixesToSchema' must be greater than or equal to 'fixesFromSchema'"); - } - this.fixesToSchema = version; - } - - public int getTargetSchema() - { - return targetSchema; - } - - /** - * Set the schema version that this patch attempts to take the existing schema to. This is for informational - * purposes only, acting as an indicator of intention rather than having any specific effect. - * - * @param version - * a schema version number that must be greater than the {@link #fixesToSchema max fix schema number} - */ - public void setTargetSchema(int version) - { - if (version <= fixesToSchema) - { - throw new IllegalArgumentException("'targetSchema' must be greater than 'fixesToSchema'"); - } - this.targetSchema = version; - } - - public String getDescription() - { - return description; - } - - /** - * @param description - * a thorough description of the patch - */ - public void setDescription(String description) - { - this.description = description; - } - - public List getDependsOn() - { - return this.dependsOn; - } - - /** - * Set all the dependencies for this patch. It should not be executed before all the dependencies have been applied. - * - * @param dependsOn - * a list of dependencies - */ - public void setDependsOn(List dependsOn) - { - this.dependsOn = dependsOn; - } - - public List getAlternatives() - { - return alternatives; - } - - /** - * Set all anti-dependencies. If any of the patches in the list have already been executed, then - * this one need not be. - * - * @param alternatives a list of alternative patches - */ - public void setAlternatives(List alternatives) - { - this.alternatives = alternatives; - } - - public boolean applies(int version) - { - return ((this.fixesFromSchema <= version) && (version <= fixesToSchema)); - } - - /** - * Performs a null check on the supplied value. - * - * @param value - * value to check - * @param name - * name of the property to report - */ - protected final void checkPropertyNotNull(Object value, String name) - { - if (value == null) - { - throw new PatchException(ERR_PROPERTY_NOT_SET, name, this); - } - } - - public void setApplyToTenants(boolean applyToTenants) - { - this.applyToTenants = applyToTenants; - } - - /** - * Check that the schema version properties have been set appropriately. Derived classes can override this method to - * perform their own validation provided that this method is called by the derived class. - */ - protected void checkProperties() - { - // check that the necessary properties have been set - checkPropertyNotNull(id, "id"); - checkPropertyNotNull(description, "description"); - checkPropertyNotNull(transactionService, "transactionService"); - checkPropertyNotNull(namespaceService, "namespaceService"); - checkPropertyNotNull(nodeService, "nodeService"); - checkPropertyNotNull(searchService, "searchService"); - checkPropertyNotNull(authenticationComponent, "authenticationComponent"); - if (fixesFromSchema == -1 || fixesToSchema == -1 || targetSchema == -1) - { - throw new AlfrescoRuntimeException( - "Patch properties 'fixesFromSchema', 'fixesToSchema' and 'targetSchema' " + - "have not all been set on this patch: \n" - + " patch: " + this); - } - } - - /** - * Sets up the transaction and ensures thread-safety. - * - * @see #applyInternal() - */ - public synchronized String apply() throws PatchException - { - // ensure that this has not been executed already - if (applied) - { - throw new AlfrescoRuntimeException("The patch has already been executed: \n" + " patch: " + this); - } - // check properties - checkProperties(); - // execute in a transaction - try - { - if (logger.isDebugEnabled()) - { - logger.debug("\n" + "Patch will be applied: \n" + " patch: " + this); - } - AuthenticationUtil.RunAsWork authorisedPathWork = new AuthenticationUtil.RunAsWork() - { - public String doWork() throws Exception - { - RetryingTransactionCallback patchWork = new RetryingTransactionCallback() - { - public String execute() throws Exception - { - - // downgrade integrity checking - IntegrityChecker.setWarnInTransaction(); - - String report = applyInternal(); - - if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants) - { - List tenants = tenantAdminService.getAllTenants(); - for (Tenant tenant : tenants) - { - String tenantDomain = tenant.getTenantDomain(); - String tenantReport = AuthenticationUtil.runAs(new RunAsWork() - { - public String doWork() throws Exception - { - return applyInternal(); - } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - - report = report + "\n" + tenantReport + " (for tenant: " + tenantDomain + ")"; - } - - return report; - } - - // done - return report; - } - }; - return transactionService.getRetryingTransactionHelper().doInTransaction(patchWork); - } - }; - startTime = System.currentTimeMillis(); - String report = AuthenticationUtil.runAs(authorisedPathWork, AuthenticationUtil.getSystemUserName()); - // the patch was successfully applied - applied = true; - // done - if (logger.isDebugEnabled()) - { - logger.debug("\n" + "Patch successfully applied: \n" + " patch: " + this + "\n" + " report: " + report); - } - return report; - } - catch (PatchException e) - { - // no need to extract the exception - throw e; - } - catch (Throwable e) - { - // check whether there is an embedded patch exception - Throwable cause = e.getCause(); - if (cause != null && cause instanceof PatchException) - { - throw (PatchException) cause; - } - // need to generate a message from the exception - String report = makeReport(e); - // generate the correct exception - throw new PatchException(report); - } - } - - /** - * Dumps the error's full message and trace to the String - * - * @param e - * the throwable - * @return Returns a String representative of the printStackTrace method - */ - private String makeReport(Throwable e) - { - StringWriter stringWriter = new StringWriter(1024); - PrintWriter printWriter = new PrintWriter(stringWriter, true); - try - { - e.printStackTrace(printWriter); - return stringWriter.toString(); - } - finally - { - printWriter.close(); - } - } - - /** - * This method does the work. All transactions and thread-safety will be taken care of by this class. Any exception - * will result in the transaction being rolled back. Integrity checks are downgraded for the duration of the - * transaction. - * - * @return Returns the report (only success messages). - * @see #apply() - * @throws Exception - * anything can be thrown. This must be used for all failures. - */ - protected abstract String applyInternal() throws Exception; - - /** - * Support to report patch completion and estimated completion time. - * - * @param estimatedTotal - * @param currentInteration - */ - protected void reportProgress(long estimatedTotal, long currentInteration) - { - if (progress_logger.isDebugEnabled()) - { - progress_logger.debug(currentInteration + "/" + estimatedTotal); - } - if (currentInteration == 0) - { - // No point reporting the start - we have already done that elsewhere .... - percentComplete = 0; - } - else if (currentInteration * 100l / estimatedTotal > percentComplete) - { - int previous = percentComplete; - percentComplete = (int) (currentInteration * 100l / estimatedTotal); - - if (percentComplete < 100) - { - // conditional report - - long currentTime = System.currentTimeMillis(); - long timeSoFar = currentTime - startTime; - long timeRemaining = timeSoFar * (100 - percentComplete) / percentComplete; - - int report = -1; - - if (timeRemaining > 60000) - { - int reportInterval = getreportingInterval(timeSoFar, timeRemaining); - - for (int i = previous + 1; i <= percentComplete; i++) - { - if (i % reportInterval == 0) - { - report = i; - } - } - if (report > 0) - { - Date end = new Date(currentTime + timeRemaining); - - String msg = I18NUtil.getMessage(MSG_PROGRESS, report, end); - progress_logger.info(msg); - } - } - } - } - } - - private int getreportingInterval(long soFar, long toGo) - { - long total = soFar + toGo; - if (total < RANGE_10) - { - return 10; - } - else if (total < RANGE_5) - { - return 5; - } - else if (total < RANGE_2) - { - return 2; - } - else - { - return 1; - } - - } -} +/* + * 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.repo.admin.patch; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.node.integrity.IntegrityChecker; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.Tenant; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Base implementation of the patch. This class ensures that the patch is thread- and transaction-safe. + * + * @author Derek Hulley + */ +public abstract class AbstractPatch implements Patch +{ + /** + * I18N message when properties not set. + *
    + *
  • {0} = property name
  • + *
  • {1} = patch instance
  • + *
+ */ + public static final String ERR_PROPERTY_NOT_SET = "patch.general.property_not_set"; + private static final String MSG_PROGRESS = "patch.progress"; + + private static final long RANGE_10 = 1000 * 60 * 90; + private static final long RANGE_5 = 1000 * 60 * 60 * 4; + private static final long RANGE_2 = 1000 * 60 * 90 * 10; + + private static Log logger = LogFactory.getLog(AbstractPatch.class); + private static Log progress_logger = LogFactory.getLog(PatchExecuter.class); + + private String id; + private int fixesFromSchema; + private int fixesToSchema; + private int targetSchema; + private String description; + /** a list of patches that this one depends on */ + private List dependsOn; + /** a list of patches that, if already present, mean that this one should be ignored */ + private List alternatives; + /** flag indicating if the patch was successfully applied */ + private boolean applied; + private boolean applyToTenants; + /** track completion * */ + int percentComplete = 0; + /** start time * */ + long startTime; + + /** the service to register ourselves with */ + private PatchService patchService; + /** used to ensure a unique transaction per execution */ + protected TransactionService transactionService; + protected NamespaceService namespaceService; + protected NodeService nodeService; + protected SearchService searchService; + protected AuthenticationContext authenticationContext; + protected TenantAdminService tenantAdminService; + + + public AbstractPatch() + { + this.fixesFromSchema = -1; + this.fixesToSchema = -1; + this.targetSchema = -1; + this.applied = false; + this.applyToTenants = true; // by default, apply to each tenant, if tenant service is enabled + this.dependsOn = Collections.emptyList(); + this.alternatives = Collections.emptyList(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(256); + sb.append("Patch") + .append("[ id=").append(id) + .append(", description=").append(description) + .append(", fixesFromSchema=").append(fixesFromSchema) + .append(", fixesToSchema=").append(fixesToSchema) + .append(", targetSchema=").append(targetSchema) + .append("]"); + return sb.toString(); + } + + /** + * Set the service that this patch will register with for execution. + */ + public void setPatchService(PatchService patchService) + { + this.patchService = patchService; + } + + /** + * Set the transaction provider so that each execution can be performed within a transaction + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + /** + * This ensures that this bean gets registered with the appropriate {@link PatchService service}. + */ + public void init() + { + if (patchService == null) + { + throw new AlfrescoRuntimeException("Mandatory property not set: patchService"); + } + patchService.registerPatch(this); + } + + public String getId() + { + return id; + } + + /** + * @param id + * the unique ID of the patch. This dictates the order in which patches are applied. + */ + public void setId(String id) + { + this.id = id; + } + + public int getFixesFromSchema() + { + return fixesFromSchema; + } + + /** + * Set the smallest schema number that this patch may be applied to. + * + * @param version + * a schema number not smaller than 0 + */ + public void setFixesFromSchema(int version) + { + if (version < 0) + { + throw new IllegalArgumentException("The 'fixesFromSchema' property may not be less than 0"); + } + this.fixesFromSchema = version; + // auto-adjust the to version + if (fixesToSchema < fixesFromSchema) + { + setFixesToSchema(this.fixesFromSchema); + } + } + + public int getFixesToSchema() + { + return fixesToSchema; + } + + /** + * Set the largest schema version number that this patch may be applied to. + * + * @param version + * a schema version number not smaller than the {@link #setFixesFromSchema(int) from version} number. + */ + public void setFixesToSchema(int version) + { + if (version < fixesFromSchema) + { + throw new IllegalArgumentException("'fixesToSchema' must be greater than or equal to 'fixesFromSchema'"); + } + this.fixesToSchema = version; + } + + public int getTargetSchema() + { + return targetSchema; + } + + /** + * Set the schema version that this patch attempts to take the existing schema to. This is for informational + * purposes only, acting as an indicator of intention rather than having any specific effect. + * + * @param version + * a schema version number that must be greater than the {@link #fixesToSchema max fix schema number} + */ + public void setTargetSchema(int version) + { + if (version <= fixesToSchema) + { + throw new IllegalArgumentException("'targetSchema' must be greater than 'fixesToSchema'"); + } + this.targetSchema = version; + } + + public String getDescription() + { + return description; + } + + /** + * @param description + * a thorough description of the patch + */ + public void setDescription(String description) + { + this.description = description; + } + + public List getDependsOn() + { + return this.dependsOn; + } + + /** + * Set all the dependencies for this patch. It should not be executed before all the dependencies have been applied. + * + * @param dependsOn + * a list of dependencies + */ + public void setDependsOn(List dependsOn) + { + this.dependsOn = dependsOn; + } + + public List getAlternatives() + { + return alternatives; + } + + /** + * Set all anti-dependencies. If any of the patches in the list have already been executed, then + * this one need not be. + * + * @param alternatives a list of alternative patches + */ + public void setAlternatives(List alternatives) + { + this.alternatives = alternatives; + } + + public boolean applies(int version) + { + return ((this.fixesFromSchema <= version) && (version <= fixesToSchema)); + } + + /** + * Performs a null check on the supplied value. + * + * @param value + * value to check + * @param name + * name of the property to report + */ + protected final void checkPropertyNotNull(Object value, String name) + { + if (value == null) + { + throw new PatchException(ERR_PROPERTY_NOT_SET, name, this); + } + } + + public void setApplyToTenants(boolean applyToTenants) + { + this.applyToTenants = applyToTenants; + } + + /** + * Check that the schema version properties have been set appropriately. Derived classes can override this method to + * perform their own validation provided that this method is called by the derived class. + */ + protected void checkProperties() + { + // check that the necessary properties have been set + checkPropertyNotNull(id, "id"); + checkPropertyNotNull(description, "description"); + checkPropertyNotNull(transactionService, "transactionService"); + checkPropertyNotNull(namespaceService, "namespaceService"); + checkPropertyNotNull(nodeService, "nodeService"); + checkPropertyNotNull(searchService, "searchService"); + checkPropertyNotNull(authenticationContext, "authenticationContext"); + if (fixesFromSchema == -1 || fixesToSchema == -1 || targetSchema == -1) + { + throw new AlfrescoRuntimeException( + "Patch properties 'fixesFromSchema', 'fixesToSchema' and 'targetSchema' " + + "have not all been set on this patch: \n" + + " patch: " + this); + } + } + + /** + * Sets up the transaction and ensures thread-safety. + * + * @see #applyInternal() + */ + public synchronized String apply() throws PatchException + { + // ensure that this has not been executed already + if (applied) + { + throw new AlfrescoRuntimeException("The patch has already been executed: \n" + " patch: " + this); + } + // check properties + checkProperties(); + // execute in a transaction + try + { + if (logger.isDebugEnabled()) + { + logger.debug("\n" + "Patch will be applied: \n" + " patch: " + this); + } + AuthenticationUtil.RunAsWork authorisedPathWork = new AuthenticationUtil.RunAsWork() + { + public String doWork() throws Exception + { + RetryingTransactionCallback patchWork = new RetryingTransactionCallback() + { + public String execute() throws Exception + { + + // downgrade integrity checking + IntegrityChecker.setWarnInTransaction(); + + String report = applyInternal(); + + if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants) + { + List tenants = tenantAdminService.getAllTenants(); + for (Tenant tenant : tenants) + { + String tenantDomain = tenant.getTenantDomain(); + String tenantReport = AuthenticationUtil.runAs(new RunAsWork() + { + public String doWork() throws Exception + { + return applyInternal(); + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + + report = report + "\n" + tenantReport + " (for tenant: " + tenantDomain + ")"; + } + + return report; + } + + // done + return report; + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(patchWork); + } + }; + startTime = System.currentTimeMillis(); + String report = AuthenticationUtil.runAs(authorisedPathWork, AuthenticationUtil.getSystemUserName()); + // the patch was successfully applied + applied = true; + // done + if (logger.isDebugEnabled()) + { + logger.debug("\n" + "Patch successfully applied: \n" + " patch: " + this + "\n" + " report: " + report); + } + return report; + } + catch (PatchException e) + { + // no need to extract the exception + throw e; + } + catch (Throwable e) + { + // check whether there is an embedded patch exception + Throwable cause = e.getCause(); + if (cause != null && cause instanceof PatchException) + { + throw (PatchException) cause; + } + // need to generate a message from the exception + String report = makeReport(e); + // generate the correct exception + throw new PatchException(report); + } + } + + /** + * Dumps the error's full message and trace to the String + * + * @param e + * the throwable + * @return Returns a String representative of the printStackTrace method + */ + private String makeReport(Throwable e) + { + StringWriter stringWriter = new StringWriter(1024); + PrintWriter printWriter = new PrintWriter(stringWriter, true); + try + { + e.printStackTrace(printWriter); + return stringWriter.toString(); + } + finally + { + printWriter.close(); + } + } + + /** + * This method does the work. All transactions and thread-safety will be taken care of by this class. Any exception + * will result in the transaction being rolled back. Integrity checks are downgraded for the duration of the + * transaction. + * + * @return Returns the report (only success messages). + * @see #apply() + * @throws Exception + * anything can be thrown. This must be used for all failures. + */ + protected abstract String applyInternal() throws Exception; + + /** + * Support to report patch completion and estimated completion time. + * + * @param estimatedTotal + * @param currentInteration + */ + protected void reportProgress(long estimatedTotal, long currentInteration) + { + if (progress_logger.isDebugEnabled()) + { + progress_logger.debug(currentInteration + "/" + estimatedTotal); + } + if (currentInteration == 0) + { + // No point reporting the start - we have already done that elsewhere .... + percentComplete = 0; + } + else if (currentInteration * 100l / estimatedTotal > percentComplete) + { + int previous = percentComplete; + percentComplete = (int) (currentInteration * 100l / estimatedTotal); + + if (percentComplete < 100) + { + // conditional report + + long currentTime = System.currentTimeMillis(); + long timeSoFar = currentTime - startTime; + long timeRemaining = timeSoFar * (100 - percentComplete) / percentComplete; + + int report = -1; + + if (timeRemaining > 60000) + { + int reportInterval = getreportingInterval(timeSoFar, timeRemaining); + + for (int i = previous + 1; i <= percentComplete; i++) + { + if (i % reportInterval == 0) + { + report = i; + } + } + if (report > 0) + { + Date end = new Date(currentTime + timeRemaining); + + String msg = I18NUtil.getMessage(MSG_PROGRESS, report, end); + progress_logger.info(msg); + } + } + } + } + } + + private int getreportingInterval(long soFar, long toGo) + { + long total = soFar + toGo; + if (total < RANGE_10) + { + return 10; + } + else if (total < RANGE_5) + { + return 5; + } + else if (total < RANGE_2) + { + return 2; + } + else + { + return 1; + } + + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/PatchTest.java b/source/java/org/alfresco/repo/admin/patch/PatchTest.java index 400049247e..1b55bf4805 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchTest.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchTest.java @@ -1,199 +1,198 @@ -/* - * 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.repo.admin.patch; - -import java.util.Date; -import java.util.List; - -import junit.framework.TestCase; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.domain.AppliedPatch; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.service.cmr.admin.PatchException; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.springframework.context.ApplicationContext; - -/** - * @see org.alfresco.repo.admin.patch.Patch - * @see org.alfresco.repo.admin.patch.AbstractPatch - * @see org.alfresco.repo.admin.patch.PatchService - * - * @author Derek Hulley - */ -public class PatchTest extends TestCase -{ - private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - private TransactionService transactionService; - private NamespaceService namespaceService; - private NodeService nodeService; - private SearchService searchService; - private AuthenticationComponent authenticationComponent; - private PatchService patchService; - private PatchDaoService patchDaoComponent; - - public PatchTest(String name) - { - super(name); - } - - public void setUp() throws Exception - { - transactionService = (TransactionService) ctx.getBean("transactionComponent"); - namespaceService = (NamespaceService) ctx.getBean("namespaceService"); - nodeService = (NodeService) ctx.getBean("nodeService"); - searchService = (SearchService) ctx.getBean("searchService"); - authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); - - patchService = (PatchService) ctx.getBean("PatchService"); - patchDaoComponent = (PatchDaoService) ctx.getBean("patchDaoComponent"); - - // get the patches to play with - patchService.registerPatch((Patch)ctx.getBean("patch.sample.02")); - patchService.registerPatch((Patch)ctx.getBean("patch.sample.01")); - } - - public void testSetup() throws Exception - { - assertNotNull(transactionService); - assertNotNull(patchService); - assertNotNull(patchDaoComponent); - } - - private SamplePatch constructSamplePatch(boolean mustFail) - { - SamplePatch patch = new SamplePatch(mustFail, transactionService); - patch.setNamespaceService(namespaceService); - patch.setNodeService(nodeService); - patch.setSearchService(searchService); - patch.setAuthenticationComponent(authenticationComponent); - // done - return patch; - } - - public void testSimplePatchSuccess() throws Exception - { - Patch patch = constructSamplePatch(false); - String report = patch.apply(); - // check that the report was generated - assertEquals("Patch report incorrect", SamplePatch.MSG_SUCCESS, report); - } - - public void testPatchReapplication() - { - // successfully apply a patch - Patch patch = constructSamplePatch(false); - patch.apply(); - // check that the patch cannot be reapplied - try - { - patch.apply(); - fail("AbstractPatch failed to prevent reapplication"); - } - catch (AlfrescoRuntimeException e) - { - // expected - } - - // apply an unsuccessful patch - patch = constructSamplePatch(true); - try - { - patch.apply(); - fail("Failed patch didn't throw PatchException"); - } - catch (PatchException e) - { - // expected - } - // repeat - try - { - patch.apply(); - fail("Reapplication of failed patch didn't throw PatchException"); - } - catch (PatchException e) - { - // expected - } - } - - public void testApplyOutstandingPatches() throws Exception - { - // apply outstanding patches - boolean success = patchService.applyOutstandingPatches(); - assertTrue(success); - // get applied patches - List appliedPatches = patchDaoComponent.getAppliedPatches(); - // check that the patch application was recorded - boolean found01 = false; - boolean found02 = false; - for (AppliedPatch appliedPatch : appliedPatches) - { - if (appliedPatch.getId().equals("Sample01")) - { - found01 = true; - assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded()); - } - else if (appliedPatch.getId().equals("Sample02")) - { - found02 = true; - assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded()); - } - } - assertTrue("Sample 01 not in list of applied patches", found01); - assertTrue("Sample 02 not in list of applied patches", found02); - } - - public void testGetPatchesByDate() throws Exception - { - // ensure that there are some applied patches - testApplyOutstandingPatches(); - // get the number of applied patches - List appliedPatches = patchDaoComponent.getAppliedPatches(); - assertTrue("Expected at least 2 applied patches", appliedPatches.size() >= 2); - - // now requery using null dates - List appliedPatchesAllDates = patchService.getPatches(null, null); - assertEquals("Applied patches by all dates doesn't match all applied patches", - appliedPatches.size(), appliedPatchesAllDates.size()); - - // make sure that the objects are not connected to the persistence layer - PatchInfo disconnectedObject = appliedPatchesAllDates.get(0); - AppliedPatch persistedObject = patchDaoComponent.getAppliedPatch(disconnectedObject.getId()); - assertNotSame("Instances should not be shared between evicted and cached objects", - disconnectedObject, persistedObject); - - // perform another query with dates that should return no results - List appliedPatchesFutureDates = patchService.getPatches(new Date(), new Date()); - assertEquals("Query returned results for dates when no patches should exist", 0, appliedPatchesFutureDates.size()); - } -} +/* + * 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.repo.admin.patch; + +import java.util.Date; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.domain.AppliedPatch; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.admin.patch.Patch + * @see org.alfresco.repo.admin.patch.AbstractPatch + * @see org.alfresco.repo.admin.patch.PatchService + * + * @author Derek Hulley + */ +public class PatchTest extends TestCase +{ + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private NamespaceService namespaceService; + private NodeService nodeService; + private SearchService searchService; + private AuthenticationContext authenticationContext; + private PatchService patchService; + private PatchDaoService patchDaoComponent; + + public PatchTest(String name) + { + super(name); + } + + public void setUp() throws Exception + { + transactionService = (TransactionService) ctx.getBean("transactionComponent"); + namespaceService = (NamespaceService) ctx.getBean("namespaceService"); + nodeService = (NodeService) ctx.getBean("nodeService"); + searchService = (SearchService) ctx.getBean("searchService"); + authenticationContext = (AuthenticationContext) ctx.getBean("authenticationContext"); + + patchService = (PatchService) ctx.getBean("PatchService"); + patchDaoComponent = (PatchDaoService) ctx.getBean("patchDaoComponent"); + + // get the patches to play with + patchService.registerPatch((Patch)ctx.getBean("patch.sample.02")); + patchService.registerPatch((Patch)ctx.getBean("patch.sample.01")); + } + + public void testSetup() throws Exception + { + assertNotNull(transactionService); + assertNotNull(patchService); + assertNotNull(patchDaoComponent); + } + + private SamplePatch constructSamplePatch(boolean mustFail) + { + SamplePatch patch = new SamplePatch(mustFail, transactionService); + patch.setNamespaceService(namespaceService); + patch.setNodeService(nodeService); + patch.setSearchService(searchService); + patch.setAuthenticationContext(authenticationContext); + // done + return patch; + } + + public void testSimplePatchSuccess() throws Exception + { + Patch patch = constructSamplePatch(false); + String report = patch.apply(); + // check that the report was generated + assertEquals("Patch report incorrect", SamplePatch.MSG_SUCCESS, report); + } + + public void testPatchReapplication() + { + // successfully apply a patch + Patch patch = constructSamplePatch(false); + patch.apply(); + // check that the patch cannot be reapplied + try + { + patch.apply(); + fail("AbstractPatch failed to prevent reapplication"); + } + catch (AlfrescoRuntimeException e) + { + // expected + } + + // apply an unsuccessful patch + patch = constructSamplePatch(true); + try + { + patch.apply(); + fail("Failed patch didn't throw PatchException"); + } + catch (PatchException e) + { + // expected + } + // repeat + try + { + patch.apply(); + fail("Reapplication of failed patch didn't throw PatchException"); + } + catch (PatchException e) + { + // expected + } + } + + public void testApplyOutstandingPatches() throws Exception + { + // apply outstanding patches + boolean success = patchService.applyOutstandingPatches(); + assertTrue(success); + // get applied patches + List appliedPatches = patchDaoComponent.getAppliedPatches(); + // check that the patch application was recorded + boolean found01 = false; + boolean found02 = false; + for (AppliedPatch appliedPatch : appliedPatches) + { + if (appliedPatch.getId().equals("Sample01")) + { + found01 = true; + assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded()); + } + else if (appliedPatch.getId().equals("Sample02")) + { + found02 = true; + assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded()); + } + } + assertTrue("Sample 01 not in list of applied patches", found01); + assertTrue("Sample 02 not in list of applied patches", found02); + } + + public void testGetPatchesByDate() throws Exception + { + // ensure that there are some applied patches + testApplyOutstandingPatches(); + // get the number of applied patches + List appliedPatches = patchDaoComponent.getAppliedPatches(); + assertTrue("Expected at least 2 applied patches", appliedPatches.size() >= 2); + + // now requery using null dates + List appliedPatchesAllDates = patchService.getPatches(null, null); + assertEquals("Applied patches by all dates doesn't match all applied patches", + appliedPatches.size(), appliedPatchesAllDates.size()); + + // make sure that the objects are not connected to the persistence layer + PatchInfo disconnectedObject = appliedPatchesAllDates.get(0); + AppliedPatch persistedObject = patchDaoComponent.getAppliedPatch(disconnectedObject.getId()); + assertNotSame("Instances should not be shared between evicted and cached objects", + disconnectedObject, persistedObject); + + // perform another query with dates that should return no results + List appliedPatchesFutureDates = patchService.getPatches(new Date(), new Date()); + assertEquals("Query returned results for dates when no patches should exist", 0, appliedPatchesFutureDates.size()); + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesContentPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesContentPatch.java index bfdf851572..66dded9214 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesContentPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesContentPatch.java @@ -181,7 +181,7 @@ public class EmailTemplatesContentPatch extends AbstractPatch return null; } }; - AuthenticationUtil.runAs(importRunAs, authenticationComponent.getSystemUserName()); + AuthenticationUtil.runAs(importRunAs, authenticationContext.getSystemUserName()); // output a message to describe the result return I18NUtil.getMessage(MSG_CREATED); diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java index 63ba9829cc..758b4f8fe8 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ScriptsFolderPatch.java @@ -232,7 +232,7 @@ public class ScriptsFolderPatch extends AbstractPatch return null; } }; - AuthenticationUtil.runAs(importRunAs, authenticationComponent.getSystemUserName()); + AuthenticationUtil.runAs(importRunAs, authenticationContext.getSystemUserName()); msg = I18NUtil.getMessage(MSG_CREATED, scriptsFolderNodeRef); } diff --git a/source/java/org/alfresco/repo/avm/AVMNodeImpl.java b/source/java/org/alfresco/repo/avm/AVMNodeImpl.java index dfdc0a11c1..865bb0b328 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeImpl.java @@ -116,10 +116,10 @@ public abstract class AVMNodeImpl implements AVMNode, Serializable fIsRoot = false; long time = System.currentTimeMillis(); String user = - RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); + RawServices.Instance().getAuthenticationContext().getCurrentUserName(); if (user == null) { - user = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); + user = RawServices.Instance().getAuthenticationContext().getSystemUserName(); } fBasicAttributes = new BasicAttributesImpl(user, user, @@ -332,10 +332,10 @@ public abstract class AVMNodeImpl implements AVMNode, Serializable checkReadOnly(); } String user = - RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); + RawServices.Instance().getAuthenticationContext().getCurrentUserName(); if (user == null) { - user = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); + user = RawServices.Instance().getAuthenticationContext().getSystemUserName(); } fBasicAttributes.setModDate(System.currentTimeMillis()); fBasicAttributes.setLastModifier(user); diff --git a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java index 6becbca014..d8f84b9ffc 100644 --- a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java @@ -1,1876 +1,1876 @@ -/* - * Copyright (C) 2005-2009 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.repo.avm; - -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMModel; -import org.alfresco.repo.avm.util.RawServices; -import org.alfresco.repo.avm.util.SimplePath; -import org.alfresco.repo.domain.DbAccessControlList; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.QNameDAO; -import org.alfresco.repo.security.permissions.ACLCopyMode; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.service.cmr.avm.AVMBadArgumentException; -import org.alfresco.service.cmr.avm.AVMException; -import org.alfresco.service.cmr.avm.AVMExistsException; -import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMNotFoundException; -import org.alfresco.service.cmr.avm.AVMStoreDescriptor; -import org.alfresco.service.cmr.avm.AVMWrongTypeException; -import org.alfresco.service.cmr.avm.VersionDescriptor; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; -import org.alfresco.util.Pair; - -/** - * A Repository contains a current root directory and a list of - * root versions. Each root version corresponds to a separate snapshot - * operation. - * @author britt - */ -public class AVMStoreImpl implements AVMStore, Serializable -{ - static final long serialVersionUID = -1485972568675732904L; - - /** - * The primary key. - */ - private long fID; - - /** - * The name of this AVMStore. - */ - private String fName; - - /** - * The current root directory. - */ - private DirectoryNode fRoot; - - /** - * The next version id. - */ - private int fNextVersionID; - - /** - * The version (for concurrency control). - */ - private long fVers; - - /** - * Acl for this store. - */ - private DbAccessControlList fACL; - - /** - * The AVMRepository. - */ - transient private AVMRepository fAVMRepository; - - /** - * Default constructor. - */ - protected AVMStoreImpl() - { - fAVMRepository = AVMRepository.GetInstance(); - } - - /** - * Make a brand new AVMStore. - * @param repo The AVMRepository. - * @param name The name of the AVMStore. - */ - public AVMStoreImpl(AVMRepository repo, String name) - { - // Make ourselves up and save. - fAVMRepository = repo; - fName = name; - fNextVersionID = 0; - fRoot = null; - AVMDAOs.Instance().fAVMStoreDAO.save(this); - String creator = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); - if (creator == null) - { - creator = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); - } - setProperty(ContentModel.PROP_CREATOR, new PropertyValue(null, creator)); - setProperty(ContentModel.PROP_CREATED, new PropertyValue(null, new Date(System.currentTimeMillis()))); - // Make up the initial version record and save. - long time = System.currentTimeMillis(); - fRoot = new PlainDirectoryNodeImpl(this); - fRoot.setIsRoot(true); - AVMDAOs.Instance().fAVMNodeDAO.save(fRoot); - VersionRoot versionRoot = new VersionRootImpl(this, - fRoot, - fNextVersionID, - time, - creator, - "Initial Empty Version.", - "Initial Empty Version."); - fNextVersionID++; - AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); - } - - /** - * Setter for hibernate. - * @param id The primary key. - */ - protected void setId(long id) - { - fID = id; - } - - /** - * Get the primary key. - * @return The primary key. - */ - public long getId() - { - return fID; - } - - /** - * Set a new root for this. - * @param root - */ - public void setNewRoot(DirectoryNode root) - { - fRoot = root; - fRoot.setIsRoot(true); - } - - /** - * Snapshot this store. This creates a new version record. - * @return The version id of the new snapshot. - */ - @SuppressWarnings("unchecked") - public Map createSnapshot(String tag, String description, Map snapShotMap) - { - long rootID = fRoot.getId(); - AVMStoreImpl me = (AVMStoreImpl)AVMDAOs.Instance().fAVMStoreDAO.getByID(fID); - VersionRoot lastVersion = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(me); - List layeredEntries = - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.get(lastVersion); - // Is there no need for a snapshot? - DirectoryNode root = (DirectoryNode)AVMDAOs.Instance().fAVMNodeDAO.getByID(rootID); - if (!root.getIsNew() && layeredEntries.size() == 0) - { - // So, we set the tag and description fields of the latest version. - if (tag != null || description != null) - { - lastVersion.setTag(tag); - lastVersion.setDescription(description); - } - snapShotMap.put(fName, lastVersion.getVersionID()); - return snapShotMap; - } - snapShotMap.put(fName, me.fNextVersionID); - // Force copies on all the layered nodes from last snapshot. - for (VersionLayeredNodeEntry entry : layeredEntries) - { - String path = entry.getPath(); - path = path.substring(path.indexOf(':') + 1); - Lookup lookup = me.lookup(-1, path, false, false); - if (lookup == null) - { - continue; - } - if (lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY && - lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_FILE) - { - continue; - } - if (lookup.getCurrentNode().getIsNew()) - { - continue; - } - fAVMRepository.forceCopy(entry.getPath()); - // TODO This leaves the behavior of LayeredFiles not quite - // right. - /* - String parentName[] = AVMNodeConverter.SplitBase(entry.getPath()); - parentName[0] = parentName[0].substring(parentName[0].indexOf(':') + 1); - lookup = lookupDirectory(-1, parentName[0], true); - DirectoryNode parent = (DirectoryNode)lookup.getCurrentNode(); - AVMNode child = parent.lookupChild(lookup, parentName[1], false); - // TODO For debugging. - if (child == null) - { - System.err.println("Yoiks!"); - } - // TODO This is funky. Need to look carefully to see that this call - // does exactly what's needed. - lookup.add(child, parentName[1], false); - AVMNode newChild = null; - if (child.getType() == AVMNodeType.LAYERED_DIRECTORY) - { - newChild = child.copy(lookup); - } - else - { - newChild = ((LayeredFileNode)child).copyLiterally(lookup); - } - parent.putChild(parentName[1], newChild); - */ - } - // Clear out the new nodes. - List allLayeredNodeIDs = AVMDAOs.Instance().fAVMNodeDAO.getNewLayeredInStoreIDs(me); - - AVMDAOs.Instance().fAVMNodeDAO.clearNewInStore(me); - - AVMDAOs.Instance().fAVMNodeDAO.clear(); - List layeredNodeIDs = new ArrayList(); - for (Long layeredID : allLayeredNodeIDs) - { - Layered layered = (Layered)AVMDAOs.Instance().fAVMNodeDAO.getByID(layeredID); - String indirection = layered.getIndirection(); - if (indirection == null) - { - continue; - } - layeredNodeIDs.add(layeredID); - String storeName = indirection.substring(0, indirection.indexOf(':')); - if (!snapShotMap.containsKey(storeName)) - { - AVMStore store = AVMDAOs.Instance().fAVMStoreDAO.getByName(storeName); - if (store == null) - { - layered.setIndirectionVersion(-1); - } - else - { - store.createSnapshot(null, null, snapShotMap); - layered = (Layered)AVMDAOs.Instance().fAVMNodeDAO.getByID(layeredID); - layered.setIndirectionVersion(snapShotMap.get(storeName)); - } - } - else - { - layered.setIndirectionVersion(snapShotMap.get(storeName)); - } - } - AVMDAOs.Instance().fAVMNodeDAO.flush(); - // Make up a new version record. - String user = RawServices.Instance().getAuthenticationComponent().getCurrentUserName(); - if (user == null) - { - user = RawServices.Instance().getAuthenticationComponent().getSystemUserName(); - } - me = (AVMStoreImpl)AVMDAOs.Instance().fAVMStoreDAO.getByID(fID); - VersionRoot versionRoot = new VersionRootImpl(me, - me.fRoot, - me.fNextVersionID++, - System.currentTimeMillis(), - user, - tag, - description); - // Another embarassing flush needed. - AVMDAOs.Instance().fAVMNodeDAO.flush(); - AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); - for (Long nodeID : layeredNodeIDs) - { - AVMNode node = AVMDAOs.Instance().fAVMNodeDAO.getByID(nodeID); - List paths = fAVMRepository.getVersionPaths(versionRoot, node); - for (String path : paths) - { - VersionLayeredNodeEntry entry = - new VersionLayeredNodeEntryImpl(versionRoot, path); - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.save(entry); - } - } - return snapShotMap; - } - - /** - * Create a new directory. - * @param path The path to the containing directory. - * @param name The name of the new directory. - */ - public void createDirectory(String path, String name, List aspects, Map properties) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - DirectoryNode newDir = null; - if (lPath.isLayered()) // Creating a directory in a layered context creates - // a LayeredDirectoryNode that gets its indirection from - // its parent. - { - newDir = new LayeredDirectoryNodeImpl((String)null, this, null, null, ACLCopyMode.INHERIT); - ((LayeredDirectoryNodeImpl)newDir).setPrimaryIndirection(false); - ((LayeredDirectoryNodeImpl)newDir).setLayerID(lPath.getTopLayer().getLayerID()); - } - else - { - newDir = new PlainDirectoryNodeImpl(this); - } - // newDir.setVersionID(getNextVersionID()); - if (child != null) - { - newDir.setAncestor(child); - } - //dir.updateModTime(); - dir.putChild(name, newDir); - if (aspects != null) - { - // Convert the aspect QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Set aspectQNameEntityIds = newDir.getAspects(); - for (QName aspectQName : aspects) - { - Long qnameEntityId = qnameDAO.getOrCreateQName(aspectQName).getFirst(); - aspectQNameEntityIds.add(qnameEntityId); - } - } - if (properties != null) - { - // Convert the property QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); - for (Map.Entry entry : properties.entrySet()) - { - Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); - propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); - } - newDir.getProperties().putAll(propertiesByQNameEntityId); - } - DbAccessControlList acl = dir.getAcl(); - newDir.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); - } - - /** - * Create a new layered directory. - * @param srcPath The target indirection for a layered node. - * @param dstPath The containing directory for the new node. - * @param name The name of the new node. - */ - public void createLayeredDirectory(String srcPath, String dstPath, - String name) - { - Lookup lPath = lookupDirectory(-1, dstPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + dstPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - Long parentAcl = dir.getAcl() == null ? null : dir.getAcl().getId(); - LayeredDirectoryNode newDir = - new LayeredDirectoryNodeImpl(srcPath, this, null, parentAcl, ACLCopyMode.INHERIT); - if (lPath.isLayered()) - { - // When a layered directory is made inside of a layered context, - // it gets its layer id from the topmost layer in its lookup - // path. - LayeredDirectoryNode top = lPath.getTopLayer(); - newDir.setLayerID(top.getLayerID()); - } - else - { - // Otherwise we issue a brand new layer id. - - // note: re-use generated node id as a layer id - newDir.setLayerID(newDir.getId()); - } - if (child != null) - { - newDir.setAncestor(child); - } - //dir.updateModTime(); - dir.putChild(name, newDir); - // newDir.setVersionID(getNextVersionID()); - } - - /** - * Create a new file. - * @param path The path to the directory to contain the new file. - * @param name The name to give the new file. - * initial content. - */ - public OutputStream createFile(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - PlainFileNodeImpl file = new PlainFileNodeImpl(this); - // file.setVersionID(getNextVersionID()); - //dir.updateModTime(); - dir.putChild(name, file); - if (child != null) - { - file.setAncestor(child); - } - file.setContentData(new ContentData(null, - RawServices.Instance().getMimetypeService().guessMimetype(name), - -1, - "UTF-8")); - DbAccessControlList acl = dir.getAcl(); - file.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); - ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); - return writer.getContentOutputStream(); - } - - /** - * Create a file with the given contents. - * @param path The path to the containing directory. - * @param name The name to give the new file. - * @param data The contents. - */ - public void createFile(String path, String name, File data, List aspects, Map properties) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - PlainFileNodeImpl file = new PlainFileNodeImpl(this); - // file.setVersionID(getNextVersionID()); - //dir.updateModTime(); - dir.putChild(name, file); - if (child != null) - { - file.setAncestor(child); - } - file.setContentData(new ContentData(null, - RawServices.Instance().getMimetypeService().guessMimetype(name), - -1, - "UTF-8")); - if (aspects != null) - { - // Convert the aspect QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Set aspectQNameEntityIds = file.getAspects(); - for (QName aspectQName : aspects) - { - Long qnameEntityId = qnameDAO.getOrCreateQName(aspectQName).getFirst(); - aspectQNameEntityIds.add(qnameEntityId); - } - } - if (properties != null) - { - // Convert the property QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); - for (Map.Entry entry : properties.entrySet()) - { - Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); - propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); - } - file.getProperties().putAll(propertiesByQNameEntityId); - } - DbAccessControlList acl = dir.getAcl(); - file.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); - // Yet another flush. - AVMDAOs.Instance().fAVMNodeDAO.flush(); - ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); - writer.putContent(data); - } - - /** - * Create a new layered file. - * @param srcPath The target indirection for the layered file. - * @param dstPath The path to the directory to contain the new file. - * @param name The name of the new file. - */ - public void createLayeredFile(String srcPath, String dstPath, String name) - { - Lookup lPath = lookupDirectory(-1, dstPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + dstPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + dstPath); - } - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child != null && child.getType() != AVMNodeType.DELETED_NODE) - { - throw new AVMExistsException("Child exists: " + name); - } - // TODO Reexamine decision to not check validity of srcPath. - LayeredFileNodeImpl newFile = - new LayeredFileNodeImpl(srcPath, this, null); - if (child != null) - { - newFile.setAncestor(child); - } - //dir.updateModTime(); - dir.putChild(name, newFile); - DbAccessControlList acl = dir.getAcl(); - newFile.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); - // newFile.setVersionID(getNextVersionID()); - } - - /** - * Get an input stream from a file. - * @param version The version id to look under. - * @param path The path to the file. - * @return An InputStream. - */ - public InputStream getInputStream(int version, String path) - { - ContentReader reader = getContentReader(version, path); - if (reader == null) - { - // TODO This is wrong, wrong, wrong. Do something about it - // sooner rather than later. - throw new AVMNotFoundException(path + " has no content."); - } - return reader.getContentInputStream(); - } - - /** - * Get a ContentReader from a file. - * @param version The version to look under. - * @param path The path to the file. - * @return A ContentReader. - */ - public ContentReader getContentReader(int version, String path) - { - NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, fName + ":" + path); - return RawServices.Instance().getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT); - } - - /** - * Get a ContentWriter to a file. - * @param path The path to the file. - * @return A ContentWriter. - */ - public ContentWriter createContentWriter(String path) - { - NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, fName + ":" + path); - ContentWriter writer = - RawServices.Instance().getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - return writer; - } - - /** - * Get a listing from a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @return A List of FolderEntries. - */ - public SortedMap getListing(int version, String path, - boolean includeDeleted) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - Map listing = dir.getListing(lPath, includeDeleted); - return translateListing(listing, lPath); - } - - /** - * Get the list of nodes directly contained in a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @return A Map of names to descriptors. - */ - public SortedMap getListingDirect(int version, String path, - boolean includeDeleted) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - if (lPath.isLayered() && dir.getType() != AVMNodeType.LAYERED_DIRECTORY) - { - return new TreeMap(); - } - Map listing = dir.getListingDirect(lPath, includeDeleted); - return translateListing(listing, lPath); - } - - /** - * Helper to convert an internal representation of a directory listing - * to an external representation. - * @param listing The internal listing, a Map of names to nodes. - * @param lPath The Lookup for the directory. - * @return A Map of names to descriptors. - */ - private SortedMap - translateListing(Map listing, Lookup lPath) - { - SortedMap results = new TreeMap(String.CASE_INSENSITIVE_ORDER); - for (String name : listing.keySet()) - { - // TODO consider doing this at a lower level. - AVMNode child = AVMNodeUnwrapper.Unwrap(listing.get(name)); - AVMNodeDescriptor desc = child.getDescriptor(lPath, name); - results.put(name, desc); - } - return results; - } - - /** - * Get the names of the deleted nodes in a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @return A List of names. - */ - public List getDeleted(int version, String path) - { - Lookup lPath = lookupDirectory(version, path, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - List deleted = dir.getDeletedNames(); - return deleted; - } - - /** - * Get an output stream to a file. - * @param path The path to the file. - * @return An OutputStream. - */ - public OutputStream getOutputStream(String path) - { - ContentWriter writer = createContentWriter(path); - return writer.getContentOutputStream(); - } - - /** - * Remove a node and everything underneath it. - * @param path The path to the containing directory. - * @param name The name of the node to remove. - */ - public void removeNode(String path, String name) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - Pair temp = dir.lookupChild(lPath, name, false); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child == null) - { - throw new AVMNotFoundException("Does not exist: " + name); - } - - if (!fAVMRepository.can(this, child, PermissionService.DELETE_NODE, false)) - { - throw new AVMNotFoundException("Not allowed to delete in store : " + getName() +" at " + path); - } - - dir.removeChild(lPath, name); - //dir.updateModTime(); - } - - /** - * Allow a name which has been deleted to be visible through that layer. - * @param dirPath The path to the containing directory. - * @param name The name to uncover. - */ - public void uncover(String dirPath, String name) - { - Lookup lPath = lookupDirectory(-1, dirPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Directory path " + dirPath + " not found."); - } - DirectoryNode node = (DirectoryNode)lPath.getCurrentNode(); - if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) - { - throw new AVMWrongTypeException("Not a layered directory: " + dirPath); - } - Pair temp = node.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if(child == null) - { - throw new AVMNotFoundException("No child to recover at "+dirPath+" called "+name); - } - if (!fAVMRepository.can(this, child, PermissionService.DELETE_NODE, false)) - { - throw new AccessDeniedException("Not allowed to uncover: " + dirPath + " -> "+name); - } - ((LayeredDirectoryNode)node).uncover(lPath, name); - node.updateModTime(); - } - - // TODO This is problematic. As time goes on this returns - // larger and larger data sets. Perhaps what we should do is - // provide methods for getting versions by date range, n most - // recent etc. - /** - * Get the set of all extant versions for this AVMStore. - * @return A Set of version ids. - */ - @SuppressWarnings("unchecked") - public List getVersions() - { - List versions = AVMDAOs.Instance().fVersionRootDAO.getAllInAVMStore(this); - List descs = new ArrayList(); - for (VersionRoot vr : versions) - { - VersionDescriptor desc = - new VersionDescriptor(fName, - vr.getVersionID(), - vr.getCreator(), - vr.getCreateDate(), - vr.getTag(), - vr.getDescription()); - descs.add(desc); - } - return descs; - } - - /** - * Get the versions between the given dates (inclusive). From or - * to may be null but not both. - * @param from The earliest date. - * @param to The latest date. - * @return The Set of matching version IDs. - */ - @SuppressWarnings("unchecked") - public List getVersions(Date from, Date to) - { - List versions = AVMDAOs.Instance().fVersionRootDAO.getByDates(this, from, to); - List descs = new ArrayList(); - for (VersionRoot vr : versions) - { - VersionDescriptor desc = - new VersionDescriptor(fName, - vr.getVersionID(), - vr.getCreator(), - vr.getCreateDate(), - vr.getTag(), - vr.getDescription()); - descs.add(desc); - } - return descs; - } - - /** - * Get the AVMRepository. - * @return The AVMRepository - */ - public AVMRepository getAVMRepository() - { - return fAVMRepository; - } - - /** - * Lookup up a path. - * @param version The version to look in. - * @param path The path to look up. - * @param write Whether this is in the context of a write. - * @return A Lookup object. - */ - public Lookup lookup(int version, String path, boolean write, boolean includeDeleted) - { - SimplePath sPath = new SimplePath(path); - return RawServices.Instance().getLookupCache().lookup(this, version, sPath, write, includeDeleted); - } - - /** - * Get the root node descriptor. - * @param version The version to get. - * @return The descriptor. - */ - public AVMNodeDescriptor getRoot(int version) - { - AVMNode root = null; - if (version < 0) - { - root = fRoot; - } - else - { - root = AVMDAOs.Instance().fAVMNodeDAO.getAVMStoreRoot(this, version); - } - if (!fAVMRepository.can(this, root, PermissionService.READ_CHILDREN, true)) - { - throw new AccessDeniedException("Not allowed to read: " + fName + "@" + version); - } - return root.getDescriptor(fName + ":", "", null, -1); - } - - /** - * Lookup a node and insist that it is a directory. - * @param version The version to look under. - * @param path The path to the directory. - * @param write Whether this is in a write context. - * @return A Lookup object. - */ - public Lookup lookupDirectory(int version, String path, boolean write) - { - // Just do a regular lookup and assert that the last element - // is a directory. - Lookup lPath = lookup(version, path, write, false); - if (lPath == null) - { - return null; - } - if (lPath.getCurrentNode().getType() != AVMNodeType.PLAIN_DIRECTORY && - lPath.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY) - { - return null; - } - return lPath; - } - - /** - * Get the effective indirection path for a layered node. - * @param version The version to look under. - * @param path The path to the node. - * @return The effective indirection. - */ - public String getIndirectionPath(int version, String path) - { - Lookup lPath = lookup(version, path, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - if (!lPath.isLayered()) - { - return null; - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) - { - LayeredDirectoryNode dir = (LayeredDirectoryNode)node; - return dir.getUnderlying(lPath); - } - else if (node.getType() == AVMNodeType.LAYERED_FILE) - { - LayeredFileNode file = (LayeredFileNode)node; - return file.getUnderlying(lPath); - } - return lPath.getIndirectionPath(); - } - - /** - * Make the indicated node a primary indirection. - * @param path The path to the node. - */ - public void makePrimary(String path) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!lPath.isLayered()) - { - throw new AVMException("Not in a layered context: " + path); - } - if (!fAVMRepository.can(this, dir, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - dir.turnPrimary(lPath); - dir.updateModTime(); - } - - /** - * Change the indirection of a layered directory. - * @param path The path to the layered directory. - * @param target The target indirection to set. - */ - public void retargetLayeredDirectory(String path, String target) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!lPath.isLayered()) - { - throw new AVMException("Not in a layered context: " + path); - } - if (!fAVMRepository.can(this, dir, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - dir.retarget(lPath, target); - dir.updateModTime(); - } - - /** - * Set the name of this AVMStore. - * @param name - */ - public void setName(String name) - { - fName = name; - } - - /** - * Get the name of this AVMStore. - * @return The name. - */ - public String getName() - { - return fName; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMStore#getAcl() - */ - public DbAccessControlList getStoreAcl() - { - return fACL; - } - - public void setStoreAcl(DbAccessControlList acl) - { - fACL = acl; - } - - /** - * Set the next version id. - * @param nextVersionID - */ - protected void setNextVersionID(int nextVersionID) - { - fNextVersionID = nextVersionID; - } - - /** - * Get the next version id. - * @return The next version id. - */ - public int getNextVersionID() - { - return fNextVersionID; - } - - /** - * This gets the last extant version id. - */ - public int getLastVersionID() - { - Integer lastVersionId = AVMDAOs.Instance().fVersionRootDAO.getMaxVersionID(this); - if (lastVersionId == null) - { - return 0; - } - else - { - return lastVersionId.intValue(); - } - } - - /** - * Set the root directory. Hibernate. - * @param root - */ - protected void setRoot(DirectoryNode root) - { - fRoot = root; - } - - /** - * Get the root directory. - * @return The root directory. - */ - public DirectoryNode getRoot() - { - return fRoot; - } - - /** - * Set the version (for concurrency control). Hibernate. - * @param vers - */ - protected void setVers(long vers) - { - fVers = vers; - } - - /** - * Get the version (for concurrency control). Hibernate. - * @return The version. - */ - protected long getVers() - { - return fVers; - } - - /** - * Equals override. - * @param obj - * @return Equality. - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (!(obj instanceof AVMStore)) - { - return false; - } - return fID == ((AVMStore)obj).getId(); - } - - /** - * Get a hash code. - * @return The hash code. - */ - @Override - public int hashCode() - { - return (int)fID; - } - - /** - * Purge all nodes reachable only via this version and repostory. - * @param version - */ - @SuppressWarnings("unchecked") - public void purgeVersion(int version) - { - if (version == 0) - { - throw new AVMBadArgumentException("Cannot purge initial version"); - } - VersionRoot vRoot = AVMDAOs.Instance().fVersionRootDAO.getByVersionID(this, version); - if (vRoot == null) - { - throw new AVMNotFoundException("Version not found."); - } - AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.delete(vRoot); - AVMNode root = vRoot.getRoot(); - if (!fAVMRepository.can(null, root, PermissionService.DELETE_CHILDREN, true)) - { - throw new AccessDeniedException("Not allowed to purge: " + fName + "@" + version); - } - root.setIsRoot(false); - AVMDAOs.Instance().fAVMNodeDAO.update(root); - AVMDAOs.Instance().fVersionRootDAO.delete(vRoot); - if (root.equals(fRoot)) - { - // We have to set a new current root. - // TODO More hibernate goofiness to compensate for: fSuper.getSession().flush(); - vRoot = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); - fRoot = vRoot.getRoot(); - AVMDAOs.Instance().fAVMStoreDAO.update(this); - } - } - - // TODO permissions? - /** - * Get the descriptor for this. - * @return An AVMStoreDescriptor - */ - public AVMStoreDescriptor getDescriptor() - { - // Get the creator ensuring that nulls are not hit - PropertyValue creatorValue = getProperty(ContentModel.PROP_CREATOR); - String creator = creatorValue == null ? "system" : (String) creatorValue.getValue(DataTypeDefinition.TEXT); - creator = (creator == null) ? "system" : creator; - // Get the created date ensuring that nulls are not hit - PropertyValue createdValue = getProperty(ContentModel.PROP_CREATED); - Date created = createdValue == null ? (new Date()) : (Date) createdValue.getValue(DataTypeDefinition.DATE); - created = (created == null) ? (new Date()) : created; - return new AVMStoreDescriptor(fName, creator, created.getTime()); - } - - /** - * Set the opacity of a layered directory. An opaque directory hides - * what is pointed at by its indirection. - * @param path The path to the layered directory. - * @param opacity True is opaque; false is not. - */ - public void setOpacity(String path, boolean opacity) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof LayeredDirectoryNode)) - { - throw new AVMWrongTypeException("Not a LayeredDirectoryNode."); - } - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - ((LayeredDirectoryNode)node).setOpacity(opacity); - node.updateModTime(); - } - - // TODO Does it make sense to set properties on DeletedNodes? - /** - * Set a property on a node. - * @param path The path to the node. - * @param name The name of the property. - * @param value The value to set. - */ - public void setNodeProperty(String path, QName name, PropertyValue value) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - Long qnameEntityId = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(name).getFirst(); - - node.setProperty(qnameEntityId, value); - node.setGuid(GUID.generate()); - } - - /** - * Set a collection of properties on a node. - * @param path The path to the node. - * @param properties The Map of QNames to PropertyValues. - */ - public void setNodeProperties(String path, Map properties) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - if (properties != null) - { - // Convert the property QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); - for (Map.Entry entry : properties.entrySet()) - { - Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); - propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); - } - node.addProperties(propertiesByQNameEntityId); - } - node.setGuid(GUID.generate()); - } - - /** - * Get a property by name. - * @param version The version to lookup. - * @param path The path to the node. - * @param name The name of the property. - * @return A PropertyValue or null if not found. - */ - public PropertyValue getNodeProperty(int version, String path, QName name) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - // Convert the QName - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Pair qnamePair = qnameDAO.getQName(name); - if (qnamePair == null) - { - // No such QName - return null; - } - else - { - PropertyValue prop = node.getProperty(qnamePair.getFirst()); - return prop; - } - } - - /** - * Get all the properties associated with a node. - * @param version The version to lookup. - * @param path The path to the node. - * @return A Map of QNames to PropertyValues. - */ - public Map getNodeProperties(int version, String path) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - Map props = node.getProperties(); - // Convert to QNames - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - @SuppressWarnings("unchecked") - Map convertedProps = (Map) qnameDAO.convertIdMapToQNameMap(props); - return convertedProps; - } - - /** - * Delete a single property from a node. - * @param path The path to the node. - * @param name The name of the property. - */ - public void deleteNodeProperty(String path, QName name) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - node.setGuid(GUID.generate()); - - // convert the QName - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Pair qnamePair = qnameDAO.getQName(name); - if (qnamePair == null) - { - // No such property - } - else - { - node.deleteProperty(qnamePair.getFirst()); - } - } - - /** - * Delete all properties from a node. - * @param path The path to the node. - */ - public void deleteNodeProperties(String path) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - node.setGuid(GUID.generate()); - node.deleteProperties(); - } - - /** - * Set a property on this store. Replaces if property already exists. - * @param name The QName of the property. - * @param value The actual PropertyValue. - */ - public void setProperty(QName name, PropertyValue value) - { - Long qnameEntityId = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(name).getFirst(); - AVMStoreProperty prop = new AVMStorePropertyImpl(); - prop.setStore(this); - prop.setQnameId(qnameEntityId); - prop.setValue(value); - AVMDAOs.Instance().fAVMStorePropertyDAO.save(prop); - } - - /** - * Set a group of properties on this store. Replaces any property that exists. - * @param properties A Map of QNames to PropertyValues to set. - */ - public void setProperties(Map properties) - { - for (QName name : properties.keySet()) - { - setProperty(name, properties.get(name)); - } - } - - /** - * Get a property by name. - * @param name The QName of the property to fetch. - * @return The PropertyValue or null if non-existent. - */ - public PropertyValue getProperty(QName name) - { - AVMStoreProperty prop = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this, name); - if (prop == null) - { - return null; - } - return prop.getValue(); - } - - /** - * Get all the properties associated with this node. - * @return A Map of the properties. - */ - @SuppressWarnings("unchecked") - public Map getProperties() - { - List props = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this); - - Map propsIdMap = new HashMap(props.size() + 7); - for (AVMStoreProperty prop : props) - { - propsIdMap.put(prop.getQnameId(), prop.getValue()); - } - // Mass-convert - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Map propsQNameMap = (Map) qnameDAO.convertIdMapToQNameMap(propsIdMap); - - return propsQNameMap; - } - - /** - * Delete a property. - * @param name The name of the property to delete. - */ - public void deleteProperty(QName name) - { - AVMDAOs.Instance().fAVMStorePropertyDAO.delete(this, name); - } - - /** - * Get the ContentData on a file. - * @param version The version to look under. - * @param path The path to the file. - * @return The ContentData corresponding to the file. - */ - public ContentData getContentDataForRead(int version, String path) - { - Lookup lPath = lookup(version, path, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - if (!fAVMRepository.can(this, node, PermissionService.READ_CONTENT, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read: " + path); - } - ContentData content = ((FileNode)node).getContentData(lPath); - // AVMDAOs.Instance().fAVMNodeDAO.flush(); - // AVMDAOs.Instance().fAVMNodeDAO.evict(node); - return content; - } - - /** - * Get the ContentData on a file for writing. - * @param path The path to the file. - * @return The ContentData corresponding to the file. - */ - public ContentData getContentDataForWrite(String path) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - if (!fAVMRepository.can(this, node, PermissionService.WRITE_CONTENT, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write content: " + path); - } - // TODO Set modifier. - node.updateModTime(); - node.setGuid(GUID.generate()); - ContentData content = ((FileNode)node).getContentData(lPath); - // AVMDAOs.Instance().fAVMNodeDAO.flush(); - // AVMDAOs.Instance().fAVMNodeDAO.evict(node); - return content; - } - - // Not doing permission checking because it will already have been done - // at the getContentDataForWrite point. - /** - * Set the ContentData for a file. - * @param path The path to the file. - * @param data The ContentData to set. - */ - public void setContentData(String path, ContentData data) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!(node instanceof FileNode)) - { - throw new AVMWrongTypeException("File Expected."); - } - ((FileNode)node).setContentData(data); - node.updateModTime(); - } - - /** - * Set meta data, aspects, properties, acls, from another node. - * @param path The path to the node to set metadata on. - * @param from The node to get the metadata from. - */ - public void setMetaDataFrom(String path, AVMNode from) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write properties: " + path); - } - node.copyMetaDataFrom(from, node.getAcl() == null ? null : node.getAcl().getInheritsFrom()); - node.setGuid(GUID.generate()); - } - - /** - * Add an aspect to a node. - * @param path The path to the node. - * @param aspectName The name of the aspect. - */ - public void addAspect(String path, QName aspectName) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write: " + path); - } - // Convert the aspect QNames to entities - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Long qnameEntityId = qnameDAO.getOrCreateQName(aspectName).getFirst(); - // Convert the - node.getAspects().add(qnameEntityId); - node.setGuid(GUID.generate()); - } - - /** - * Get all aspects on a given node. - * @param version The version to look under. - * @param path The path to the node. - * @return A List of the QNames of the aspects. - */ - public Set getAspects(int version, String path) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read properties: " + path); - } - Set aspects = node.getAspects(); - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Set aspectQNames = qnameDAO.convertIdsToQNames(aspects); - return aspectQNames; - } - - /** - * Remove an aspect and all its properties from a node. - * @param path The path to the node. - * @param aspectName The name of the aspect. - */ - public void removeAspect(String path, QName aspectName) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write properties: " + path); - } - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - // Get the persistent ID for the QName and remove from the set - Pair qnamePair = qnameDAO.getQName(aspectName); - if (qnamePair != null) - { - node.getAspects().remove(qnamePair.getFirst()); - } - AspectDefinition def = RawServices.Instance().getDictionaryService().getAspect(aspectName); - Map properties = def.getProperties(); - Set propertyQNameIds = qnameDAO.convertQNamesToIds(properties.keySet(), false); - - Map nodeProperties = node.getProperties(); - for (Long propertyQNameId : propertyQNameIds) - { - nodeProperties.remove(propertyQNameId); - } - node.setGuid(GUID.generate()); - } - - /** - * Does a given node have a given aspect. - * @param version The version to look under. - * @param path The path to the node. - * @param aspectName The name of the aspect. - * @return Whether the node has the aspect. - */ - public boolean hasAspect(int version, String path, QName aspectName) - { - Lookup lPath = lookup(version, path, false, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read properties: " + path); - } - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - // Get the persistent ID for the QName and remove from the set - Pair qnamePair = qnameDAO.getQName(aspectName); - if (qnamePair != null) - { - return node.getAspects().contains(qnamePair.getFirst()); - } - else - { - return false; - } - } - - /** - * Set the ACL on a node. - * @param path The path to the node. - * @param acl The ACL to set. - */ - public void setACL(String path, DbAccessControlList acl) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.CHANGE_PERMISSIONS, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to change permissions: " + path); - } - node.setAcl(acl); - node.setGuid(GUID.generate()); - } - - /** - * Get the ACL on a node. - * @param version The version to look under. - * @param path The path to the node. - * @return The ACL. - */ - public DbAccessControlList getACL(int version, String path) - { - Lookup lPath = lookup(version, path, false, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - if (!fAVMRepository.can(this, lPath.getCurrentNode(), PermissionService.READ_PERMISSIONS, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to read permissions: " + path + " in "+getName()); - } - return lPath.getCurrentNode().getAcl(); - } - - /** - * Link a node into a directory, directly. - * @param parentPath The path to the directory. - * @param name The name to give the parent. - * @param toLink The node to link. - */ - public void link(String parentPath, String name, AVMNodeDescriptor toLink) - { - Lookup lPath = lookupDirectory(-1, parentPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + parentPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - if (!fAVMRepository.can(null, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to add children: " + parentPath); - } - dir.link(lPath, name, toLink); - } - - /** - * Update a link to a node in a directory, directly. - * @param parentPath The path to the directory. - * @param name The name to give the parent. - * @param toLink The node to link. - */ - public void updateLink(String parentPath, String name, AVMNodeDescriptor toLink) - { - Lookup lPath = lookupDirectory(-1, parentPath, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + parentPath + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - - Lookup cPath = new Lookup(lPath, AVMDAOs.Instance().fAVMNodeDAO, AVMDAOs.Instance().fAVMStoreDAO); - Pair result = dir.lookupChild(cPath, name, true); - if (result == null) - { - throw new AVMNotFoundException("Path " + parentPath + "/" +name + " not found."); - } - AVMNode child = result.getFirst(); - if (!fAVMRepository.can(null, child, PermissionService.WRITE, cPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to update node: " + parentPath + "/" +name ); - } - - dir.removeChild(lPath, name); - dir.link(lPath, name, toLink); - } - - /** - * Revert a head path to a given version. This works by cloning - * the version to revert to, and then linking that new version into head. - * The reverted version will have the previous head version as ancestor. - * @param path The path to the parent directory. - * @param name The name of the node to revert. - * @param toRevertTo The descriptor of the version to revert to. - */ - public void revert(String path, String name, AVMNodeDescriptor toRevertTo) - { - Lookup lPath = lookupDirectory(-1, path, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path " + path + " not found."); - } - DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); - - Pair temp = dir.lookupChild(lPath, name, true); - AVMNode child = (temp == null) ? null : temp.getFirst(); - if (child == null) - { - throw new AVMNotFoundException("Node not found: " + name); - } - if (!fAVMRepository.can(null, child, PermissionService.WRITE, false)) - { - throw new AccessDeniedException("Not allowed to revert: " + path); - } - AVMNode revertNode = AVMDAOs.Instance().fAVMNodeDAO.getByID(toRevertTo.getId()); - if (revertNode == null) - { - throw new AVMNotFoundException(toRevertTo.toString()); - } - AVMNode toLink = revertNode.copy(lPath); - dir.putChild(name, toLink); - toLink.changeAncestor(child); - toLink.setVersionID(child.getVersionID() + 1); - // TODO This really shouldn't be here. Leaking layers. - QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; - Pair revertedQNamePair = qnameDAO.getOrCreateQName(WCMModel.ASPECT_REVERTED); - toLink.getAspects().add(revertedQNamePair.getFirst()); - PropertyValue value = new PropertyValue(null, toRevertTo.getId()); - - Pair qnamePair = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(WCMModel.PROP_REVERTED_ID); - toLink.setProperty(qnamePair.getFirst(), value); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMStore#setGuid(java.lang.String, java.lang.String) - */ - public void setGuid(String path, String guid) - { - Lookup lPath = lookup(-1, path, true, true); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write properties: " + path); - } - node.setGuid(guid); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMStore#setEncoding(java.lang.String, java.lang.String) - */ - public void setEncoding(String path, String encoding) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - if (node.getType() != AVMNodeType.PLAIN_FILE) - { - throw new AVMWrongTypeException("Not a File: " + path); - } - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write properties: " + path); - } - PlainFileNode file = (PlainFileNode)node; - file.setEncoding(encoding); - } - - /* (non-Javadoc) - * @see org.alfresco.repo.avm.AVMStore#setMimeType(java.lang.String, java.lang.String) - */ - public void setMimeType(String path, String mimeType) - { - Lookup lPath = lookup(-1, path, true, false); - if (lPath == null) - { - throw new AVMNotFoundException("Path not found: " + path); - } - AVMNode node = lPath.getCurrentNode(); - if (node.getType() != AVMNodeType.PLAIN_FILE) - { - throw new AVMWrongTypeException("Not a File: " + path); - } - if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) - { - throw new AccessDeniedException("Not allowed to write properties: " + path); - } - PlainFileNode file = (PlainFileNode)node; - file.setMimeType(mimeType); - } -} +/* + * Copyright (C) 2005-2009 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.repo.avm; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.util.RawServices; +import org.alfresco.repo.avm.util.SimplePath; +import org.alfresco.repo.domain.DbAccessControlList; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.QNameDAO; +import org.alfresco.repo.security.permissions.ACLCopyMode; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.avm.AVMBadArgumentException; +import org.alfresco.service.cmr.avm.AVMException; +import org.alfresco.service.cmr.avm.AVMExistsException; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; +import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.cmr.avm.AVMWrongTypeException; +import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.alfresco.util.Pair; + +/** + * A Repository contains a current root directory and a list of + * root versions. Each root version corresponds to a separate snapshot + * operation. + * @author britt + */ +public class AVMStoreImpl implements AVMStore, Serializable +{ + static final long serialVersionUID = -1485972568675732904L; + + /** + * The primary key. + */ + private long fID; + + /** + * The name of this AVMStore. + */ + private String fName; + + /** + * The current root directory. + */ + private DirectoryNode fRoot; + + /** + * The next version id. + */ + private int fNextVersionID; + + /** + * The version (for concurrency control). + */ + private long fVers; + + /** + * Acl for this store. + */ + private DbAccessControlList fACL; + + /** + * The AVMRepository. + */ + transient private AVMRepository fAVMRepository; + + /** + * Default constructor. + */ + protected AVMStoreImpl() + { + fAVMRepository = AVMRepository.GetInstance(); + } + + /** + * Make a brand new AVMStore. + * @param repo The AVMRepository. + * @param name The name of the AVMStore. + */ + public AVMStoreImpl(AVMRepository repo, String name) + { + // Make ourselves up and save. + fAVMRepository = repo; + fName = name; + fNextVersionID = 0; + fRoot = null; + AVMDAOs.Instance().fAVMStoreDAO.save(this); + String creator = RawServices.Instance().getAuthenticationContext().getCurrentUserName(); + if (creator == null) + { + creator = RawServices.Instance().getAuthenticationContext().getSystemUserName(); + } + setProperty(ContentModel.PROP_CREATOR, new PropertyValue(null, creator)); + setProperty(ContentModel.PROP_CREATED, new PropertyValue(null, new Date(System.currentTimeMillis()))); + // Make up the initial version record and save. + long time = System.currentTimeMillis(); + fRoot = new PlainDirectoryNodeImpl(this); + fRoot.setIsRoot(true); + AVMDAOs.Instance().fAVMNodeDAO.save(fRoot); + VersionRoot versionRoot = new VersionRootImpl(this, + fRoot, + fNextVersionID, + time, + creator, + "Initial Empty Version.", + "Initial Empty Version."); + fNextVersionID++; + AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); + } + + /** + * Setter for hibernate. + * @param id The primary key. + */ + protected void setId(long id) + { + fID = id; + } + + /** + * Get the primary key. + * @return The primary key. + */ + public long getId() + { + return fID; + } + + /** + * Set a new root for this. + * @param root + */ + public void setNewRoot(DirectoryNode root) + { + fRoot = root; + fRoot.setIsRoot(true); + } + + /** + * Snapshot this store. This creates a new version record. + * @return The version id of the new snapshot. + */ + @SuppressWarnings("unchecked") + public Map createSnapshot(String tag, String description, Map snapShotMap) + { + long rootID = fRoot.getId(); + AVMStoreImpl me = (AVMStoreImpl)AVMDAOs.Instance().fAVMStoreDAO.getByID(fID); + VersionRoot lastVersion = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(me); + List layeredEntries = + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.get(lastVersion); + // Is there no need for a snapshot? + DirectoryNode root = (DirectoryNode)AVMDAOs.Instance().fAVMNodeDAO.getByID(rootID); + if (!root.getIsNew() && layeredEntries.size() == 0) + { + // So, we set the tag and description fields of the latest version. + if (tag != null || description != null) + { + lastVersion.setTag(tag); + lastVersion.setDescription(description); + } + snapShotMap.put(fName, lastVersion.getVersionID()); + return snapShotMap; + } + snapShotMap.put(fName, me.fNextVersionID); + // Force copies on all the layered nodes from last snapshot. + for (VersionLayeredNodeEntry entry : layeredEntries) + { + String path = entry.getPath(); + path = path.substring(path.indexOf(':') + 1); + Lookup lookup = me.lookup(-1, path, false, false); + if (lookup == null) + { + continue; + } + if (lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY && + lookup.getCurrentNode().getType() != AVMNodeType.LAYERED_FILE) + { + continue; + } + if (lookup.getCurrentNode().getIsNew()) + { + continue; + } + fAVMRepository.forceCopy(entry.getPath()); + // TODO This leaves the behavior of LayeredFiles not quite + // right. + /* + String parentName[] = AVMNodeConverter.SplitBase(entry.getPath()); + parentName[0] = parentName[0].substring(parentName[0].indexOf(':') + 1); + lookup = lookupDirectory(-1, parentName[0], true); + DirectoryNode parent = (DirectoryNode)lookup.getCurrentNode(); + AVMNode child = parent.lookupChild(lookup, parentName[1], false); + // TODO For debugging. + if (child == null) + { + System.err.println("Yoiks!"); + } + // TODO This is funky. Need to look carefully to see that this call + // does exactly what's needed. + lookup.add(child, parentName[1], false); + AVMNode newChild = null; + if (child.getType() == AVMNodeType.LAYERED_DIRECTORY) + { + newChild = child.copy(lookup); + } + else + { + newChild = ((LayeredFileNode)child).copyLiterally(lookup); + } + parent.putChild(parentName[1], newChild); + */ + } + // Clear out the new nodes. + List allLayeredNodeIDs = AVMDAOs.Instance().fAVMNodeDAO.getNewLayeredInStoreIDs(me); + + AVMDAOs.Instance().fAVMNodeDAO.clearNewInStore(me); + + AVMDAOs.Instance().fAVMNodeDAO.clear(); + List layeredNodeIDs = new ArrayList(); + for (Long layeredID : allLayeredNodeIDs) + { + Layered layered = (Layered)AVMDAOs.Instance().fAVMNodeDAO.getByID(layeredID); + String indirection = layered.getIndirection(); + if (indirection == null) + { + continue; + } + layeredNodeIDs.add(layeredID); + String storeName = indirection.substring(0, indirection.indexOf(':')); + if (!snapShotMap.containsKey(storeName)) + { + AVMStore store = AVMDAOs.Instance().fAVMStoreDAO.getByName(storeName); + if (store == null) + { + layered.setIndirectionVersion(-1); + } + else + { + store.createSnapshot(null, null, snapShotMap); + layered = (Layered)AVMDAOs.Instance().fAVMNodeDAO.getByID(layeredID); + layered.setIndirectionVersion(snapShotMap.get(storeName)); + } + } + else + { + layered.setIndirectionVersion(snapShotMap.get(storeName)); + } + } + AVMDAOs.Instance().fAVMNodeDAO.flush(); + // Make up a new version record. + String user = RawServices.Instance().getAuthenticationContext().getCurrentUserName(); + if (user == null) + { + user = RawServices.Instance().getAuthenticationContext().getSystemUserName(); + } + me = (AVMStoreImpl)AVMDAOs.Instance().fAVMStoreDAO.getByID(fID); + VersionRoot versionRoot = new VersionRootImpl(me, + me.fRoot, + me.fNextVersionID++, + System.currentTimeMillis(), + user, + tag, + description); + // Another embarassing flush needed. + AVMDAOs.Instance().fAVMNodeDAO.flush(); + AVMDAOs.Instance().fVersionRootDAO.save(versionRoot); + for (Long nodeID : layeredNodeIDs) + { + AVMNode node = AVMDAOs.Instance().fAVMNodeDAO.getByID(nodeID); + List paths = fAVMRepository.getVersionPaths(versionRoot, node); + for (String path : paths) + { + VersionLayeredNodeEntry entry = + new VersionLayeredNodeEntryImpl(versionRoot, path); + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.save(entry); + } + } + return snapShotMap; + } + + /** + * Create a new directory. + * @param path The path to the containing directory. + * @param name The name of the new directory. + */ + public void createDirectory(String path, String name, List aspects, Map properties) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + DirectoryNode newDir = null; + if (lPath.isLayered()) // Creating a directory in a layered context creates + // a LayeredDirectoryNode that gets its indirection from + // its parent. + { + newDir = new LayeredDirectoryNodeImpl((String)null, this, null, null, ACLCopyMode.INHERIT); + ((LayeredDirectoryNodeImpl)newDir).setPrimaryIndirection(false); + ((LayeredDirectoryNodeImpl)newDir).setLayerID(lPath.getTopLayer().getLayerID()); + } + else + { + newDir = new PlainDirectoryNodeImpl(this); + } + // newDir.setVersionID(getNextVersionID()); + if (child != null) + { + newDir.setAncestor(child); + } + //dir.updateModTime(); + dir.putChild(name, newDir); + if (aspects != null) + { + // Convert the aspect QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Set aspectQNameEntityIds = newDir.getAspects(); + for (QName aspectQName : aspects) + { + Long qnameEntityId = qnameDAO.getOrCreateQName(aspectQName).getFirst(); + aspectQNameEntityIds.add(qnameEntityId); + } + } + if (properties != null) + { + // Convert the property QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); + for (Map.Entry entry : properties.entrySet()) + { + Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); + propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); + } + newDir.getProperties().putAll(propertiesByQNameEntityId); + } + DbAccessControlList acl = dir.getAcl(); + newDir.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); + } + + /** + * Create a new layered directory. + * @param srcPath The target indirection for a layered node. + * @param dstPath The containing directory for the new node. + * @param name The name of the new node. + */ + public void createLayeredDirectory(String srcPath, String dstPath, + String name) + { + Lookup lPath = lookupDirectory(-1, dstPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + dstPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + Long parentAcl = dir.getAcl() == null ? null : dir.getAcl().getId(); + LayeredDirectoryNode newDir = + new LayeredDirectoryNodeImpl(srcPath, this, null, parentAcl, ACLCopyMode.INHERIT); + if (lPath.isLayered()) + { + // When a layered directory is made inside of a layered context, + // it gets its layer id from the topmost layer in its lookup + // path. + LayeredDirectoryNode top = lPath.getTopLayer(); + newDir.setLayerID(top.getLayerID()); + } + else + { + // Otherwise we issue a brand new layer id. + + // note: re-use generated node id as a layer id + newDir.setLayerID(newDir.getId()); + } + if (child != null) + { + newDir.setAncestor(child); + } + //dir.updateModTime(); + dir.putChild(name, newDir); + // newDir.setVersionID(getNextVersionID()); + } + + /** + * Create a new file. + * @param path The path to the directory to contain the new file. + * @param name The name to give the new file. + * initial content. + */ + public OutputStream createFile(String path, String name) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + PlainFileNodeImpl file = new PlainFileNodeImpl(this); + // file.setVersionID(getNextVersionID()); + //dir.updateModTime(); + dir.putChild(name, file); + if (child != null) + { + file.setAncestor(child); + } + file.setContentData(new ContentData(null, + RawServices.Instance().getMimetypeService().guessMimetype(name), + -1, + "UTF-8")); + DbAccessControlList acl = dir.getAcl(); + file.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); + ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); + return writer.getContentOutputStream(); + } + + /** + * Create a file with the given contents. + * @param path The path to the containing directory. + * @param name The name to give the new file. + * @param data The contents. + */ + public void createFile(String path, String name, File data, List aspects, Map properties) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + PlainFileNodeImpl file = new PlainFileNodeImpl(this); + // file.setVersionID(getNextVersionID()); + //dir.updateModTime(); + dir.putChild(name, file); + if (child != null) + { + file.setAncestor(child); + } + file.setContentData(new ContentData(null, + RawServices.Instance().getMimetypeService().guessMimetype(name), + -1, + "UTF-8")); + if (aspects != null) + { + // Convert the aspect QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Set aspectQNameEntityIds = file.getAspects(); + for (QName aspectQName : aspects) + { + Long qnameEntityId = qnameDAO.getOrCreateQName(aspectQName).getFirst(); + aspectQNameEntityIds.add(qnameEntityId); + } + } + if (properties != null) + { + // Convert the property QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); + for (Map.Entry entry : properties.entrySet()) + { + Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); + propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); + } + file.getProperties().putAll(propertiesByQNameEntityId); + } + DbAccessControlList acl = dir.getAcl(); + file.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); + // Yet another flush. + AVMDAOs.Instance().fAVMNodeDAO.flush(); + ContentWriter writer = createContentWriter(AVMNodeConverter.ExtendAVMPath(path, name)); + writer.putContent(data); + } + + /** + * Create a new layered file. + * @param srcPath The target indirection for the layered file. + * @param dstPath The path to the directory to contain the new file. + * @param name The name of the new file. + */ + public void createLayeredFile(String srcPath, String dstPath, String name) + { + Lookup lPath = lookupDirectory(-1, dstPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + dstPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + dstPath); + } + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child != null && child.getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMExistsException("Child exists: " + name); + } + // TODO Reexamine decision to not check validity of srcPath. + LayeredFileNodeImpl newFile = + new LayeredFileNodeImpl(srcPath, this, null); + if (child != null) + { + newFile.setAncestor(child); + } + //dir.updateModTime(); + dir.putChild(name, newFile); + DbAccessControlList acl = dir.getAcl(); + newFile.setAcl(acl != null ? acl.getCopy(acl.getId(), ACLCopyMode.INHERIT) : null); + // newFile.setVersionID(getNextVersionID()); + } + + /** + * Get an input stream from a file. + * @param version The version id to look under. + * @param path The path to the file. + * @return An InputStream. + */ + public InputStream getInputStream(int version, String path) + { + ContentReader reader = getContentReader(version, path); + if (reader == null) + { + // TODO This is wrong, wrong, wrong. Do something about it + // sooner rather than later. + throw new AVMNotFoundException(path + " has no content."); + } + return reader.getContentInputStream(); + } + + /** + * Get a ContentReader from a file. + * @param version The version to look under. + * @param path The path to the file. + * @return A ContentReader. + */ + public ContentReader getContentReader(int version, String path) + { + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(version, fName + ":" + path); + return RawServices.Instance().getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT); + } + + /** + * Get a ContentWriter to a file. + * @param path The path to the file. + * @return A ContentWriter. + */ + public ContentWriter createContentWriter(String path) + { + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, fName + ":" + path); + ContentWriter writer = + RawServices.Instance().getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + return writer; + } + + /** + * Get a listing from a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A List of FolderEntries. + */ + public SortedMap getListing(int version, String path, + boolean includeDeleted) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + Map listing = dir.getListing(lPath, includeDeleted); + return translateListing(listing, lPath); + } + + /** + * Get the list of nodes directly contained in a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A Map of names to descriptors. + */ + public SortedMap getListingDirect(int version, String path, + boolean includeDeleted) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + if (lPath.isLayered() && dir.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + return new TreeMap(); + } + Map listing = dir.getListingDirect(lPath, includeDeleted); + return translateListing(listing, lPath); + } + + /** + * Helper to convert an internal representation of a directory listing + * to an external representation. + * @param listing The internal listing, a Map of names to nodes. + * @param lPath The Lookup for the directory. + * @return A Map of names to descriptors. + */ + private SortedMap + translateListing(Map listing, Lookup lPath) + { + SortedMap results = new TreeMap(String.CASE_INSENSITIVE_ORDER); + for (String name : listing.keySet()) + { + // TODO consider doing this at a lower level. + AVMNode child = AVMNodeUnwrapper.Unwrap(listing.get(name)); + AVMNodeDescriptor desc = child.getDescriptor(lPath, name); + results.put(name, desc); + } + return results; + } + + /** + * Get the names of the deleted nodes in a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @return A List of names. + */ + public List getDeleted(int version, String path) + { + Lookup lPath = lookupDirectory(version, path, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(this, dir, PermissionService.READ_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + List deleted = dir.getDeletedNames(); + return deleted; + } + + /** + * Get an output stream to a file. + * @param path The path to the file. + * @return An OutputStream. + */ + public OutputStream getOutputStream(String path) + { + ContentWriter writer = createContentWriter(path); + return writer.getContentOutputStream(); + } + + /** + * Remove a node and everything underneath it. + * @param path The path to the containing directory. + * @param name The name of the node to remove. + */ + public void removeNode(String path, String name) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + Pair temp = dir.lookupChild(lPath, name, false); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child == null) + { + throw new AVMNotFoundException("Does not exist: " + name); + } + + if (!fAVMRepository.can(this, child, PermissionService.DELETE_NODE, false)) + { + throw new AVMNotFoundException("Not allowed to delete in store : " + getName() +" at " + path); + } + + dir.removeChild(lPath, name); + //dir.updateModTime(); + } + + /** + * Allow a name which has been deleted to be visible through that layer. + * @param dirPath The path to the containing directory. + * @param name The name to uncover. + */ + public void uncover(String dirPath, String name) + { + Lookup lPath = lookupDirectory(-1, dirPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Directory path " + dirPath + " not found."); + } + DirectoryNode node = (DirectoryNode)lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.LAYERED_DIRECTORY) + { + throw new AVMWrongTypeException("Not a layered directory: " + dirPath); + } + Pair temp = node.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if(child == null) + { + throw new AVMNotFoundException("No child to recover at "+dirPath+" called "+name); + } + if (!fAVMRepository.can(this, child, PermissionService.DELETE_NODE, false)) + { + throw new AccessDeniedException("Not allowed to uncover: " + dirPath + " -> "+name); + } + ((LayeredDirectoryNode)node).uncover(lPath, name); + node.updateModTime(); + } + + // TODO This is problematic. As time goes on this returns + // larger and larger data sets. Perhaps what we should do is + // provide methods for getting versions by date range, n most + // recent etc. + /** + * Get the set of all extant versions for this AVMStore. + * @return A Set of version ids. + */ + @SuppressWarnings("unchecked") + public List getVersions() + { + List versions = AVMDAOs.Instance().fVersionRootDAO.getAllInAVMStore(this); + List descs = new ArrayList(); + for (VersionRoot vr : versions) + { + VersionDescriptor desc = + new VersionDescriptor(fName, + vr.getVersionID(), + vr.getCreator(), + vr.getCreateDate(), + vr.getTag(), + vr.getDescription()); + descs.add(desc); + } + return descs; + } + + /** + * Get the versions between the given dates (inclusive). From or + * to may be null but not both. + * @param from The earliest date. + * @param to The latest date. + * @return The Set of matching version IDs. + */ + @SuppressWarnings("unchecked") + public List getVersions(Date from, Date to) + { + List versions = AVMDAOs.Instance().fVersionRootDAO.getByDates(this, from, to); + List descs = new ArrayList(); + for (VersionRoot vr : versions) + { + VersionDescriptor desc = + new VersionDescriptor(fName, + vr.getVersionID(), + vr.getCreator(), + vr.getCreateDate(), + vr.getTag(), + vr.getDescription()); + descs.add(desc); + } + return descs; + } + + /** + * Get the AVMRepository. + * @return The AVMRepository + */ + public AVMRepository getAVMRepository() + { + return fAVMRepository; + } + + /** + * Lookup up a path. + * @param version The version to look in. + * @param path The path to look up. + * @param write Whether this is in the context of a write. + * @return A Lookup object. + */ + public Lookup lookup(int version, String path, boolean write, boolean includeDeleted) + { + SimplePath sPath = new SimplePath(path); + return RawServices.Instance().getLookupCache().lookup(this, version, sPath, write, includeDeleted); + } + + /** + * Get the root node descriptor. + * @param version The version to get. + * @return The descriptor. + */ + public AVMNodeDescriptor getRoot(int version) + { + AVMNode root = null; + if (version < 0) + { + root = fRoot; + } + else + { + root = AVMDAOs.Instance().fAVMNodeDAO.getAVMStoreRoot(this, version); + } + if (!fAVMRepository.can(this, root, PermissionService.READ_CHILDREN, true)) + { + throw new AccessDeniedException("Not allowed to read: " + fName + "@" + version); + } + return root.getDescriptor(fName + ":", "", null, -1); + } + + /** + * Lookup a node and insist that it is a directory. + * @param version The version to look under. + * @param path The path to the directory. + * @param write Whether this is in a write context. + * @return A Lookup object. + */ + public Lookup lookupDirectory(int version, String path, boolean write) + { + // Just do a regular lookup and assert that the last element + // is a directory. + Lookup lPath = lookup(version, path, write, false); + if (lPath == null) + { + return null; + } + if (lPath.getCurrentNode().getType() != AVMNodeType.PLAIN_DIRECTORY && + lPath.getCurrentNode().getType() != AVMNodeType.LAYERED_DIRECTORY) + { + return null; + } + return lPath; + } + + /** + * Get the effective indirection path for a layered node. + * @param version The version to look under. + * @param path The path to the node. + * @return The effective indirection. + */ + public String getIndirectionPath(int version, String path) + { + Lookup lPath = lookup(version, path, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + if (!lPath.isLayered()) + { + return null; + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + if (node.getType() == AVMNodeType.LAYERED_DIRECTORY) + { + LayeredDirectoryNode dir = (LayeredDirectoryNode)node; + return dir.getUnderlying(lPath); + } + else if (node.getType() == AVMNodeType.LAYERED_FILE) + { + LayeredFileNode file = (LayeredFileNode)node; + return file.getUnderlying(lPath); + } + return lPath.getIndirectionPath(); + } + + /** + * Make the indicated node a primary indirection. + * @param path The path to the node. + */ + public void makePrimary(String path) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + if (!fAVMRepository.can(this, dir, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + dir.turnPrimary(lPath); + dir.updateModTime(); + } + + /** + * Change the indirection of a layered directory. + * @param path The path to the layered directory. + * @param target The target indirection to set. + */ + public void retargetLayeredDirectory(String path, String target) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!lPath.isLayered()) + { + throw new AVMException("Not in a layered context: " + path); + } + if (!fAVMRepository.can(this, dir, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + dir.retarget(lPath, target); + dir.updateModTime(); + } + + /** + * Set the name of this AVMStore. + * @param name + */ + public void setName(String name) + { + fName = name; + } + + /** + * Get the name of this AVMStore. + * @return The name. + */ + public String getName() + { + return fName; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMStore#getAcl() + */ + public DbAccessControlList getStoreAcl() + { + return fACL; + } + + public void setStoreAcl(DbAccessControlList acl) + { + fACL = acl; + } + + /** + * Set the next version id. + * @param nextVersionID + */ + protected void setNextVersionID(int nextVersionID) + { + fNextVersionID = nextVersionID; + } + + /** + * Get the next version id. + * @return The next version id. + */ + public int getNextVersionID() + { + return fNextVersionID; + } + + /** + * This gets the last extant version id. + */ + public int getLastVersionID() + { + Integer lastVersionId = AVMDAOs.Instance().fVersionRootDAO.getMaxVersionID(this); + if (lastVersionId == null) + { + return 0; + } + else + { + return lastVersionId.intValue(); + } + } + + /** + * Set the root directory. Hibernate. + * @param root + */ + protected void setRoot(DirectoryNode root) + { + fRoot = root; + } + + /** + * Get the root directory. + * @return The root directory. + */ + public DirectoryNode getRoot() + { + return fRoot; + } + + /** + * Set the version (for concurrency control). Hibernate. + * @param vers + */ + protected void setVers(long vers) + { + fVers = vers; + } + + /** + * Get the version (for concurrency control). Hibernate. + * @return The version. + */ + protected long getVers() + { + return fVers; + } + + /** + * Equals override. + * @param obj + * @return Equality. + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (!(obj instanceof AVMStore)) + { + return false; + } + return fID == ((AVMStore)obj).getId(); + } + + /** + * Get a hash code. + * @return The hash code. + */ + @Override + public int hashCode() + { + return (int)fID; + } + + /** + * Purge all nodes reachable only via this version and repostory. + * @param version + */ + @SuppressWarnings("unchecked") + public void purgeVersion(int version) + { + if (version == 0) + { + throw new AVMBadArgumentException("Cannot purge initial version"); + } + VersionRoot vRoot = AVMDAOs.Instance().fVersionRootDAO.getByVersionID(this, version); + if (vRoot == null) + { + throw new AVMNotFoundException("Version not found."); + } + AVMDAOs.Instance().fVersionLayeredNodeEntryDAO.delete(vRoot); + AVMNode root = vRoot.getRoot(); + if (!fAVMRepository.can(null, root, PermissionService.DELETE_CHILDREN, true)) + { + throw new AccessDeniedException("Not allowed to purge: " + fName + "@" + version); + } + root.setIsRoot(false); + AVMDAOs.Instance().fAVMNodeDAO.update(root); + AVMDAOs.Instance().fVersionRootDAO.delete(vRoot); + if (root.equals(fRoot)) + { + // We have to set a new current root. + // TODO More hibernate goofiness to compensate for: fSuper.getSession().flush(); + vRoot = AVMDAOs.Instance().fVersionRootDAO.getMaxVersion(this); + fRoot = vRoot.getRoot(); + AVMDAOs.Instance().fAVMStoreDAO.update(this); + } + } + + // TODO permissions? + /** + * Get the descriptor for this. + * @return An AVMStoreDescriptor + */ + public AVMStoreDescriptor getDescriptor() + { + // Get the creator ensuring that nulls are not hit + PropertyValue creatorValue = getProperty(ContentModel.PROP_CREATOR); + String creator = creatorValue == null ? "system" : (String) creatorValue.getValue(DataTypeDefinition.TEXT); + creator = (creator == null) ? "system" : creator; + // Get the created date ensuring that nulls are not hit + PropertyValue createdValue = getProperty(ContentModel.PROP_CREATED); + Date created = createdValue == null ? (new Date()) : (Date) createdValue.getValue(DataTypeDefinition.DATE); + created = (created == null) ? (new Date()) : created; + return new AVMStoreDescriptor(fName, creator, created.getTime()); + } + + /** + * Set the opacity of a layered directory. An opaque directory hides + * what is pointed at by its indirection. + * @param path The path to the layered directory. + * @param opacity True is opaque; false is not. + */ + public void setOpacity(String path, boolean opacity) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof LayeredDirectoryNode)) + { + throw new AVMWrongTypeException("Not a LayeredDirectoryNode."); + } + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + ((LayeredDirectoryNode)node).setOpacity(opacity); + node.updateModTime(); + } + + // TODO Does it make sense to set properties on DeletedNodes? + /** + * Set a property on a node. + * @param path The path to the node. + * @param name The name of the property. + * @param value The value to set. + */ + public void setNodeProperty(String path, QName name, PropertyValue value) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + Long qnameEntityId = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(name).getFirst(); + + node.setProperty(qnameEntityId, value); + node.setGuid(GUID.generate()); + } + + /** + * Set a collection of properties on a node. + * @param path The path to the node. + * @param properties The Map of QNames to PropertyValues. + */ + public void setNodeProperties(String path, Map properties) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + if (properties != null) + { + // Convert the property QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Map propertiesByQNameEntityId = new HashMap(properties.size() * 2 + 1); + for (Map.Entry entry : properties.entrySet()) + { + Long qnameEntityId = qnameDAO.getOrCreateQName(entry.getKey()).getFirst(); + propertiesByQNameEntityId.put(qnameEntityId, entry.getValue()); + } + node.addProperties(propertiesByQNameEntityId); + } + node.setGuid(GUID.generate()); + } + + /** + * Get a property by name. + * @param version The version to lookup. + * @param path The path to the node. + * @param name The name of the property. + * @return A PropertyValue or null if not found. + */ + public PropertyValue getNodeProperty(int version, String path, QName name) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + // Convert the QName + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Pair qnamePair = qnameDAO.getQName(name); + if (qnamePair == null) + { + // No such QName + return null; + } + else + { + PropertyValue prop = node.getProperty(qnamePair.getFirst()); + return prop; + } + } + + /** + * Get all the properties associated with a node. + * @param version The version to lookup. + * @param path The path to the node. + * @return A Map of QNames to PropertyValues. + */ + public Map getNodeProperties(int version, String path) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + Map props = node.getProperties(); + // Convert to QNames + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + @SuppressWarnings("unchecked") + Map convertedProps = (Map) qnameDAO.convertIdMapToQNameMap(props); + return convertedProps; + } + + /** + * Delete a single property from a node. + * @param path The path to the node. + * @param name The name of the property. + */ + public void deleteNodeProperty(String path, QName name) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + node.setGuid(GUID.generate()); + + // convert the QName + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Pair qnamePair = qnameDAO.getQName(name); + if (qnamePair == null) + { + // No such property + } + else + { + node.deleteProperty(qnamePair.getFirst()); + } + } + + /** + * Delete all properties from a node. + * @param path The path to the node. + */ + public void deleteNodeProperties(String path) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + node.setGuid(GUID.generate()); + node.deleteProperties(); + } + + /** + * Set a property on this store. Replaces if property already exists. + * @param name The QName of the property. + * @param value The actual PropertyValue. + */ + public void setProperty(QName name, PropertyValue value) + { + Long qnameEntityId = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(name).getFirst(); + AVMStoreProperty prop = new AVMStorePropertyImpl(); + prop.setStore(this); + prop.setQnameId(qnameEntityId); + prop.setValue(value); + AVMDAOs.Instance().fAVMStorePropertyDAO.save(prop); + } + + /** + * Set a group of properties on this store. Replaces any property that exists. + * @param properties A Map of QNames to PropertyValues to set. + */ + public void setProperties(Map properties) + { + for (QName name : properties.keySet()) + { + setProperty(name, properties.get(name)); + } + } + + /** + * Get a property by name. + * @param name The QName of the property to fetch. + * @return The PropertyValue or null if non-existent. + */ + public PropertyValue getProperty(QName name) + { + AVMStoreProperty prop = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this, name); + if (prop == null) + { + return null; + } + return prop.getValue(); + } + + /** + * Get all the properties associated with this node. + * @return A Map of the properties. + */ + @SuppressWarnings("unchecked") + public Map getProperties() + { + List props = AVMDAOs.Instance().fAVMStorePropertyDAO.get(this); + + Map propsIdMap = new HashMap(props.size() + 7); + for (AVMStoreProperty prop : props) + { + propsIdMap.put(prop.getQnameId(), prop.getValue()); + } + // Mass-convert + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Map propsQNameMap = (Map) qnameDAO.convertIdMapToQNameMap(propsIdMap); + + return propsQNameMap; + } + + /** + * Delete a property. + * @param name The name of the property to delete. + */ + public void deleteProperty(QName name) + { + AVMDAOs.Instance().fAVMStorePropertyDAO.delete(this, name); + } + + /** + * Get the ContentData on a file. + * @param version The version to look under. + * @param path The path to the file. + * @return The ContentData corresponding to the file. + */ + public ContentData getContentDataForRead(int version, String path) + { + Lookup lPath = lookup(version, path, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + if (!fAVMRepository.can(this, node, PermissionService.READ_CONTENT, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read: " + path); + } + ContentData content = ((FileNode)node).getContentData(lPath); + // AVMDAOs.Instance().fAVMNodeDAO.flush(); + // AVMDAOs.Instance().fAVMNodeDAO.evict(node); + return content; + } + + /** + * Get the ContentData on a file for writing. + * @param path The path to the file. + * @return The ContentData corresponding to the file. + */ + public ContentData getContentDataForWrite(String path) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + if (!fAVMRepository.can(this, node, PermissionService.WRITE_CONTENT, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write content: " + path); + } + // TODO Set modifier. + node.updateModTime(); + node.setGuid(GUID.generate()); + ContentData content = ((FileNode)node).getContentData(lPath); + // AVMDAOs.Instance().fAVMNodeDAO.flush(); + // AVMDAOs.Instance().fAVMNodeDAO.evict(node); + return content; + } + + // Not doing permission checking because it will already have been done + // at the getContentDataForWrite point. + /** + * Set the ContentData for a file. + * @param path The path to the file. + * @param data The ContentData to set. + */ + public void setContentData(String path, ContentData data) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!(node instanceof FileNode)) + { + throw new AVMWrongTypeException("File Expected."); + } + ((FileNode)node).setContentData(data); + node.updateModTime(); + } + + /** + * Set meta data, aspects, properties, acls, from another node. + * @param path The path to the node to set metadata on. + * @param from The node to get the metadata from. + */ + public void setMetaDataFrom(String path, AVMNode from) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write properties: " + path); + } + node.copyMetaDataFrom(from, node.getAcl() == null ? null : node.getAcl().getInheritsFrom()); + node.setGuid(GUID.generate()); + } + + /** + * Add an aspect to a node. + * @param path The path to the node. + * @param aspectName The name of the aspect. + */ + public void addAspect(String path, QName aspectName) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write: " + path); + } + // Convert the aspect QNames to entities + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Long qnameEntityId = qnameDAO.getOrCreateQName(aspectName).getFirst(); + // Convert the + node.getAspects().add(qnameEntityId); + node.setGuid(GUID.generate()); + } + + /** + * Get all aspects on a given node. + * @param version The version to look under. + * @param path The path to the node. + * @return A List of the QNames of the aspects. + */ + public Set getAspects(int version, String path) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read properties: " + path); + } + Set aspects = node.getAspects(); + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Set aspectQNames = qnameDAO.convertIdsToQNames(aspects); + return aspectQNames; + } + + /** + * Remove an aspect and all its properties from a node. + * @param path The path to the node. + * @param aspectName The name of the aspect. + */ + public void removeAspect(String path, QName aspectName) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write properties: " + path); + } + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + // Get the persistent ID for the QName and remove from the set + Pair qnamePair = qnameDAO.getQName(aspectName); + if (qnamePair != null) + { + node.getAspects().remove(qnamePair.getFirst()); + } + AspectDefinition def = RawServices.Instance().getDictionaryService().getAspect(aspectName); + Map properties = def.getProperties(); + Set propertyQNameIds = qnameDAO.convertQNamesToIds(properties.keySet(), false); + + Map nodeProperties = node.getProperties(); + for (Long propertyQNameId : propertyQNameIds) + { + nodeProperties.remove(propertyQNameId); + } + node.setGuid(GUID.generate()); + } + + /** + * Does a given node have a given aspect. + * @param version The version to look under. + * @param path The path to the node. + * @param aspectName The name of the aspect. + * @return Whether the node has the aspect. + */ + public boolean hasAspect(int version, String path, QName aspectName) + { + Lookup lPath = lookup(version, path, false, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.READ_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read properties: " + path); + } + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + // Get the persistent ID for the QName and remove from the set + Pair qnamePair = qnameDAO.getQName(aspectName); + if (qnamePair != null) + { + return node.getAspects().contains(qnamePair.getFirst()); + } + else + { + return false; + } + } + + /** + * Set the ACL on a node. + * @param path The path to the node. + * @param acl The ACL to set. + */ + public void setACL(String path, DbAccessControlList acl) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.CHANGE_PERMISSIONS, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to change permissions: " + path); + } + node.setAcl(acl); + node.setGuid(GUID.generate()); + } + + /** + * Get the ACL on a node. + * @param version The version to look under. + * @param path The path to the node. + * @return The ACL. + */ + public DbAccessControlList getACL(int version, String path) + { + Lookup lPath = lookup(version, path, false, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + if (!fAVMRepository.can(this, lPath.getCurrentNode(), PermissionService.READ_PERMISSIONS, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to read permissions: " + path + " in "+getName()); + } + return lPath.getCurrentNode().getAcl(); + } + + /** + * Link a node into a directory, directly. + * @param parentPath The path to the directory. + * @param name The name to give the parent. + * @param toLink The node to link. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink) + { + Lookup lPath = lookupDirectory(-1, parentPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + parentPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + if (!fAVMRepository.can(null, dir, PermissionService.ADD_CHILDREN, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to add children: " + parentPath); + } + dir.link(lPath, name, toLink); + } + + /** + * Update a link to a node in a directory, directly. + * @param parentPath The path to the directory. + * @param name The name to give the parent. + * @param toLink The node to link. + */ + public void updateLink(String parentPath, String name, AVMNodeDescriptor toLink) + { + Lookup lPath = lookupDirectory(-1, parentPath, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + parentPath + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + + Lookup cPath = new Lookup(lPath, AVMDAOs.Instance().fAVMNodeDAO, AVMDAOs.Instance().fAVMStoreDAO); + Pair result = dir.lookupChild(cPath, name, true); + if (result == null) + { + throw new AVMNotFoundException("Path " + parentPath + "/" +name + " not found."); + } + AVMNode child = result.getFirst(); + if (!fAVMRepository.can(null, child, PermissionService.WRITE, cPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to update node: " + parentPath + "/" +name ); + } + + dir.removeChild(lPath, name); + dir.link(lPath, name, toLink); + } + + /** + * Revert a head path to a given version. This works by cloning + * the version to revert to, and then linking that new version into head. + * The reverted version will have the previous head version as ancestor. + * @param path The path to the parent directory. + * @param name The name of the node to revert. + * @param toRevertTo The descriptor of the version to revert to. + */ + public void revert(String path, String name, AVMNodeDescriptor toRevertTo) + { + Lookup lPath = lookupDirectory(-1, path, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path " + path + " not found."); + } + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + + Pair temp = dir.lookupChild(lPath, name, true); + AVMNode child = (temp == null) ? null : temp.getFirst(); + if (child == null) + { + throw new AVMNotFoundException("Node not found: " + name); + } + if (!fAVMRepository.can(null, child, PermissionService.WRITE, false)) + { + throw new AccessDeniedException("Not allowed to revert: " + path); + } + AVMNode revertNode = AVMDAOs.Instance().fAVMNodeDAO.getByID(toRevertTo.getId()); + if (revertNode == null) + { + throw new AVMNotFoundException(toRevertTo.toString()); + } + AVMNode toLink = revertNode.copy(lPath); + dir.putChild(name, toLink); + toLink.changeAncestor(child); + toLink.setVersionID(child.getVersionID() + 1); + // TODO This really shouldn't be here. Leaking layers. + QNameDAO qnameDAO = AVMDAOs.Instance().fQNameDAO; + Pair revertedQNamePair = qnameDAO.getOrCreateQName(WCMModel.ASPECT_REVERTED); + toLink.getAspects().add(revertedQNamePair.getFirst()); + PropertyValue value = new PropertyValue(null, toRevertTo.getId()); + + Pair qnamePair = AVMDAOs.Instance().fQNameDAO.getOrCreateQName(WCMModel.PROP_REVERTED_ID); + toLink.setProperty(qnamePair.getFirst(), value); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMStore#setGuid(java.lang.String, java.lang.String) + */ + public void setGuid(String path, String guid) + { + Lookup lPath = lookup(-1, path, true, true); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write properties: " + path); + } + node.setGuid(guid); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMStore#setEncoding(java.lang.String, java.lang.String) + */ + public void setEncoding(String path, String encoding) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.PLAIN_FILE) + { + throw new AVMWrongTypeException("Not a File: " + path); + } + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write properties: " + path); + } + PlainFileNode file = (PlainFileNode)node; + file.setEncoding(encoding); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.avm.AVMStore#setMimeType(java.lang.String, java.lang.String) + */ + public void setMimeType(String path, String mimeType) + { + Lookup lPath = lookup(-1, path, true, false); + if (lPath == null) + { + throw new AVMNotFoundException("Path not found: " + path); + } + AVMNode node = lPath.getCurrentNode(); + if (node.getType() != AVMNodeType.PLAIN_FILE) + { + throw new AVMWrongTypeException("Not a File: " + path); + } + if (!fAVMRepository.can(this, node, PermissionService.WRITE_PROPERTIES, lPath.getDirectlyContained())) + { + throw new AccessDeniedException("Not allowed to write properties: " + path); + } + PlainFileNode file = (PlainFileNode)node; + file.setMimeType(mimeType); + } +} diff --git a/source/java/org/alfresco/repo/avm/util/RawServices.java b/source/java/org/alfresco/repo/avm/util/RawServices.java index d73440e8d7..d6d4263342 100644 --- a/source/java/org/alfresco/repo/avm/util/RawServices.java +++ b/source/java/org/alfresco/repo/avm/util/RawServices.java @@ -5,7 +5,7 @@ package org.alfresco.repo.avm.util; import org.alfresco.repo.avm.LookupCache; import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.MimetypeService; @@ -30,9 +30,9 @@ public class RawServices implements ApplicationContextAware private ApplicationContext fContext; /** - * The AuthenticationComponent. + * The AuthenticationContext. */ - private AuthenticationComponent fAuthenticationComponent; + private AuthenticationContext fAuthenticationContext; /** * The Content Service. @@ -82,14 +82,14 @@ public class RawServices implements ApplicationContextAware fContext = applicationContext; } - public AuthenticationComponent getAuthenticationComponent() + public AuthenticationContext getAuthenticationContext() { - if (fAuthenticationComponent == null) + if (fAuthenticationContext == null) { - fAuthenticationComponent = - (AuthenticationComponent)fContext.getBean("authenticationComponent"); + fAuthenticationContext = + (AuthenticationContext)fContext.getBean("authenticationContext"); } - return fAuthenticationComponent; + return fAuthenticationContext; } public ContentService getContentService() diff --git a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java index 1463cd1538..8249c0d9d0 100644 --- a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java +++ b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java @@ -1,513 +1,513 @@ -/* - * Copyright (C) 2005-2009 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.repo.config.xml; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import javax.transaction.UserTransaction; - -import org.alfresco.config.ConfigDeployment; -import org.alfresco.config.ConfigImpl; -import org.alfresco.config.ConfigSection; -import org.alfresco.config.ConfigSource; -import org.alfresco.config.evaluator.Evaluator; -import org.alfresco.config.xml.XMLConfigService; -import org.alfresco.config.xml.elementreader.ConfigElementReader; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; - -/** - * XML-based configuration service which can optionally read config from the Repository - * - */ -public class RepoXMLConfigService extends XMLConfigService implements TenantDeployer -{ - private static final Log logger = LogFactory.getLog(RepoXMLConfigService.class); - - /** - * Lock objects - */ - private ReadWriteLock lock = new ReentrantReadWriteLock(); - private Lock readLock = lock.readLock(); - private Lock writeLock = lock.writeLock(); - - // Dependencies - private TransactionService transactionService; - private AuthenticationComponent authenticationComponent; - private TenantAdminService tenantAdminService; - - // Internal cache (clusterable) - private SimpleCache configDataCache; - - // used to reset the cache - private ThreadLocal configDataThreadLocal = new ThreadLocal(); - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - public void setConfigDataCache(SimpleCache configDataCache) - { - this.configDataCache = configDataCache; - } - - - /** - * Constructs an XMLConfigService using the given config source - * - * @param configSource - * A ConfigSource - */ - public RepoXMLConfigService(ConfigSource configSource) - { - super(configSource); - } - - public List initConfig() - { - return resetRepoConfig().getConfigDeployments(); - } - - private ConfigData initRepoConfig(String tenantDomain) - { - ConfigData configData = null; - - // can be null e.g. initial login, after fresh bootstrap - String currentUser = authenticationComponent.getCurrentUserName(); - if (currentUser == null) - { - authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - } - - UserTransaction userTransaction = transactionService.getUserTransaction(); - - try - { - userTransaction.begin(); - - // parse config - List configDeployments = super.initConfig(); - - configData = getConfigDataLocal(tenantDomain); - if (configData != null) - { - configData.setConfigDeployments(configDeployments); - } - - userTransaction.commit(); - - logger.info("Config initialised"); - } - catch(Throwable e) - { - try { userTransaction.rollback(); } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to initialise config service", e); - } - finally - { - if (currentUser == null) - { - authenticationComponent.clearCurrentSecurityContext(); - } - } - - return configData; - } - - public void destroy() - { - super.destroy(); - - logger.info("Config destroyed"); - } - - /** - * Resets the config service - */ - public void reset() - { - resetRepoConfig(); - } - - /** - * Resets the config service - */ - private ConfigData resetRepoConfig() - { - if (logger.isDebugEnabled()) - { - logger.debug("Resetting repo config service"); - } - - String tenantDomain = getTenantDomain(); - try - { - destroy(); - - // create threadlocal, if needed - ConfigData configData = getConfigDataLocal(tenantDomain); - if (configData == null) - { - configData = new ConfigData(tenantDomain); - this.configDataThreadLocal.set(configData); - } - - configData = initRepoConfig(tenantDomain); - - if (configData == null) - { - // unexpected - throw new AlfrescoRuntimeException("Failed to reset configData " + tenantDomain); - } - - try - { - writeLock.lock(); - configDataCache.put(tenantDomain, configData); - } - finally - { - writeLock.unlock(); - } - - return configData; - } - finally - { - try - { - readLock.lock(); - if (configDataCache.get(tenantDomain) != null) - { - this.configDataThreadLocal.set(null); // it's in the cache, clear the threadlocal - } - } - finally - { - readLock.unlock(); - } - } - } - - - @Override - protected void onBootstrap(ApplicationEvent event) - { - // run as System on bootstrap - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - initConfig(); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - if ((tenantAdminService != null) && (tenantAdminService.isEnabled())) - { - tenantAdminService.deployTenants(this, logger); - tenantAdminService.register(this); - } - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } - - public void onEnableTenant() - { - initConfig(); // will be called in context of tenant - } - - public void onDisableTenant() - { - destroy(); // will be called in context of tenant - } - - // re-entrant (eg. via reset) - private ConfigData getConfigData() - { - String tenantDomain = getTenantDomain(); - - // check threadlocal first - return if set - ConfigData configData = getConfigDataLocal(tenantDomain); - if (configData != null) - { - return configData; // return local config - } - - try - { - // check cache second - return if set - readLock.lock(); - configData = configDataCache.get(tenantDomain); - - if (configData != null) - { - return configData; // return cached config - } - } - finally - { - readLock.unlock(); - } - - // reset caches - may have been invalidated (e.g. in a cluster) - configData = resetRepoConfig(); - - if (configData == null) - { - // unexpected - throw new AlfrescoRuntimeException("Failed to get configData " + tenantDomain); - } - - return configData; - } - - // get threadlocal - private ConfigData getConfigDataLocal(String tenantDomain) - { - ConfigData configData = this.configDataThreadLocal.get(); - - // check to see if domain switched (eg. during login) - if ((configData != null) && (tenantDomain.equals(configData.getTenantDomain()))) - { - return configData; // return threadlocal, if set - } - - return null; - } - - private void removeConfigData() - { - try - { - writeLock.lock(); - String tenantDomain = getTenantDomain(); - if (configDataCache.get(tenantDomain) != null) - { - configDataCache.remove(tenantDomain); - } - } - finally - { - writeLock.unlock(); - } - } - - @Override - protected ConfigImpl getGlobalConfigImpl() - { - return getConfigData().getGlobalConfig(); - } - - @Override - protected void putGlobalConfig(ConfigImpl globalConfig) - { - getConfigData().setGlobalConfig(globalConfig); - } - - @Override - protected void removeGlobalConfig() - { - removeConfigData(); - } - - @Override - protected Map getEvaluators() - { - return getConfigData().getEvaluators(); - } - - @Override - protected void putEvaluators(Map evaluators) - { - getConfigData().setEvaluators(evaluators); - } - - @Override - protected void removeEvaluators() - { - removeConfigData(); - } - - @Override - protected Map> getSectionsByArea() - { - return getConfigData().getSectionsByArea(); - } - - @Override - protected void putSectionsByArea(Map> sectionsByArea) - { - getConfigData().setSectionsByArea(sectionsByArea); - } - - @Override - protected void removeSectionsByArea() - { - removeConfigData(); - } - - @Override - protected List getSections() - { - return getConfigData().getSections(); - } - - @Override - protected void putSections(List sections) - { - getConfigData().setSections(sections); - } - - @Override - protected void removeSections() - { - removeConfigData(); - } - - @Override - protected Map getElementReaders() - { - return getConfigData().getElementReaders(); - } - - @Override - protected void putElementReaders(Map elementReaders) - { - getConfigData().setElementReaders(elementReaders); - } - - @Override - protected void removeElementReaders() - { - removeConfigData(); - } - - // local helper - returns tenant domain (or empty string if default non-tenant) - private String getTenantDomain() - { - return tenantAdminService.getCurrentUserDomain(); - } - - private class ConfigData - { - private ConfigImpl globalConfig; - private Map evaluators; - private Map> sectionsByArea; - private List sections; - private Map elementReaders; - - private List configDeployments; - - private String tenantDomain; - - public ConfigData(String tenantDomain) - { - this.tenantDomain = tenantDomain; - } - - public String getTenantDomain() - { - return tenantDomain; - } - - public ConfigImpl getGlobalConfig() - { - return globalConfig; - } - public void setGlobalConfig(ConfigImpl globalConfig) - { - this.globalConfig = globalConfig; - } - public Map getEvaluators() - { - return evaluators; - } - public void setEvaluators(Map evaluators) - { - this.evaluators = evaluators; - } - public Map> getSectionsByArea() - { - return sectionsByArea; - } - public void setSectionsByArea(Map> sectionsByArea) - { - this.sectionsByArea = sectionsByArea; - } - public List getSections() - { - return sections; - } - public void setSections(List sections) - { - this.sections = sections; - } - public Map getElementReaders() - { - return elementReaders; - } - public void setElementReaders(Map elementReaders) - { - this.elementReaders = elementReaders; - } - public List getConfigDeployments() - { - return configDeployments; - } - public void setConfigDeployments(List configDeployments) - { - this.configDeployments = configDeployments; - } - } -} +/* + * Copyright (C) 2005-2009 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.repo.config.xml; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigDeployment; +import org.alfresco.config.ConfigImpl; +import org.alfresco.config.ConfigSection; +import org.alfresco.config.ConfigSource; +import org.alfresco.config.evaluator.Evaluator; +import org.alfresco.config.xml.XMLConfigService; +import org.alfresco.config.xml.elementreader.ConfigElementReader; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; + +/** + * XML-based configuration service which can optionally read config from the Repository + * + */ +public class RepoXMLConfigService extends XMLConfigService implements TenantDeployer +{ + private static final Log logger = LogFactory.getLog(RepoXMLConfigService.class); + + /** + * Lock objects + */ + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private Lock readLock = lock.readLock(); + private Lock writeLock = lock.writeLock(); + + // Dependencies + private TransactionService transactionService; + private AuthenticationContext authenticationContext; + private TenantAdminService tenantAdminService; + + // Internal cache (clusterable) + private SimpleCache configDataCache; + + // used to reset the cache + private ThreadLocal configDataThreadLocal = new ThreadLocal(); + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + public void setConfigDataCache(SimpleCache configDataCache) + { + this.configDataCache = configDataCache; + } + + + /** + * Constructs an XMLConfigService using the given config source + * + * @param configSource + * A ConfigSource + */ + public RepoXMLConfigService(ConfigSource configSource) + { + super(configSource); + } + + public List initConfig() + { + return resetRepoConfig().getConfigDeployments(); + } + + private ConfigData initRepoConfig(String tenantDomain) + { + ConfigData configData = null; + + // can be null e.g. initial login, after fresh bootstrap + String currentUser = authenticationContext.getCurrentUserName(); + if (currentUser == null) + { + authenticationContext.setSystemUserAsCurrentUser(); + } + + UserTransaction userTransaction = transactionService.getUserTransaction(); + + try + { + userTransaction.begin(); + + // parse config + List configDeployments = super.initConfig(); + + configData = getConfigDataLocal(tenantDomain); + if (configData != null) + { + configData.setConfigDeployments(configDeployments); + } + + userTransaction.commit(); + + logger.info("Config initialised"); + } + catch(Throwable e) + { + try { userTransaction.rollback(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to initialise config service", e); + } + finally + { + if (currentUser == null) + { + authenticationContext.clearCurrentSecurityContext(); + } + } + + return configData; + } + + public void destroy() + { + super.destroy(); + + logger.info("Config destroyed"); + } + + /** + * Resets the config service + */ + public void reset() + { + resetRepoConfig(); + } + + /** + * Resets the config service + */ + private ConfigData resetRepoConfig() + { + if (logger.isDebugEnabled()) + { + logger.debug("Resetting repo config service"); + } + + String tenantDomain = getTenantDomain(); + try + { + destroy(); + + // create threadlocal, if needed + ConfigData configData = getConfigDataLocal(tenantDomain); + if (configData == null) + { + configData = new ConfigData(tenantDomain); + this.configDataThreadLocal.set(configData); + } + + configData = initRepoConfig(tenantDomain); + + if (configData == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to reset configData " + tenantDomain); + } + + try + { + writeLock.lock(); + configDataCache.put(tenantDomain, configData); + } + finally + { + writeLock.unlock(); + } + + return configData; + } + finally + { + try + { + readLock.lock(); + if (configDataCache.get(tenantDomain) != null) + { + this.configDataThreadLocal.set(null); // it's in the cache, clear the threadlocal + } + } + finally + { + readLock.unlock(); + } + } + } + + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // run as System on bootstrap + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + initConfig(); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if ((tenantAdminService != null) && (tenantAdminService.isEnabled())) + { + tenantAdminService.deployTenants(this, logger); + tenantAdminService.register(this); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + + public void onEnableTenant() + { + initConfig(); // will be called in context of tenant + } + + public void onDisableTenant() + { + destroy(); // will be called in context of tenant + } + + // re-entrant (eg. via reset) + private ConfigData getConfigData() + { + String tenantDomain = getTenantDomain(); + + // check threadlocal first - return if set + ConfigData configData = getConfigDataLocal(tenantDomain); + if (configData != null) + { + return configData; // return local config + } + + try + { + // check cache second - return if set + readLock.lock(); + configData = configDataCache.get(tenantDomain); + + if (configData != null) + { + return configData; // return cached config + } + } + finally + { + readLock.unlock(); + } + + // reset caches - may have been invalidated (e.g. in a cluster) + configData = resetRepoConfig(); + + if (configData == null) + { + // unexpected + throw new AlfrescoRuntimeException("Failed to get configData " + tenantDomain); + } + + return configData; + } + + // get threadlocal + private ConfigData getConfigDataLocal(String tenantDomain) + { + ConfigData configData = this.configDataThreadLocal.get(); + + // check to see if domain switched (eg. during login) + if ((configData != null) && (tenantDomain.equals(configData.getTenantDomain()))) + { + return configData; // return threadlocal, if set + } + + return null; + } + + private void removeConfigData() + { + try + { + writeLock.lock(); + String tenantDomain = getTenantDomain(); + if (configDataCache.get(tenantDomain) != null) + { + configDataCache.remove(tenantDomain); + } + } + finally + { + writeLock.unlock(); + } + } + + @Override + protected ConfigImpl getGlobalConfigImpl() + { + return getConfigData().getGlobalConfig(); + } + + @Override + protected void putGlobalConfig(ConfigImpl globalConfig) + { + getConfigData().setGlobalConfig(globalConfig); + } + + @Override + protected void removeGlobalConfig() + { + removeConfigData(); + } + + @Override + protected Map getEvaluators() + { + return getConfigData().getEvaluators(); + } + + @Override + protected void putEvaluators(Map evaluators) + { + getConfigData().setEvaluators(evaluators); + } + + @Override + protected void removeEvaluators() + { + removeConfigData(); + } + + @Override + protected Map> getSectionsByArea() + { + return getConfigData().getSectionsByArea(); + } + + @Override + protected void putSectionsByArea(Map> sectionsByArea) + { + getConfigData().setSectionsByArea(sectionsByArea); + } + + @Override + protected void removeSectionsByArea() + { + removeConfigData(); + } + + @Override + protected List getSections() + { + return getConfigData().getSections(); + } + + @Override + protected void putSections(List sections) + { + getConfigData().setSections(sections); + } + + @Override + protected void removeSections() + { + removeConfigData(); + } + + @Override + protected Map getElementReaders() + { + return getConfigData().getElementReaders(); + } + + @Override + protected void putElementReaders(Map elementReaders) + { + getConfigData().setElementReaders(elementReaders); + } + + @Override + protected void removeElementReaders() + { + removeConfigData(); + } + + // local helper - returns tenant domain (or empty string if default non-tenant) + private String getTenantDomain() + { + return tenantAdminService.getCurrentUserDomain(); + } + + private class ConfigData + { + private ConfigImpl globalConfig; + private Map evaluators; + private Map> sectionsByArea; + private List sections; + private Map elementReaders; + + private List configDeployments; + + private String tenantDomain; + + public ConfigData(String tenantDomain) + { + this.tenantDomain = tenantDomain; + } + + public String getTenantDomain() + { + return tenantDomain; + } + + public ConfigImpl getGlobalConfig() + { + return globalConfig; + } + public void setGlobalConfig(ConfigImpl globalConfig) + { + this.globalConfig = globalConfig; + } + public Map getEvaluators() + { + return evaluators; + } + public void setEvaluators(Map evaluators) + { + this.evaluators = evaluators; + } + public Map> getSectionsByArea() + { + return sectionsByArea; + } + public void setSectionsByArea(Map> sectionsByArea) + { + this.sectionsByArea = sectionsByArea; + } + public List getSections() + { + return sections; + } + public void setSections(List sections) + { + this.sections = sections; + } + public Map getElementReaders() + { + return elementReaders; + } + public void setElementReaders(Map elementReaders) + { + this.elementReaders = elementReaders; + } + public List getConfigDeployments() + { + return configDeployments; + } + public void setConfigDeployments(List configDeployments) + { + this.configDeployments = configDeployments; + } + } +} diff --git a/source/java/org/alfresco/repo/importer/ExportSourceImporter.java b/source/java/org/alfresco/repo/importer/ExportSourceImporter.java index 2d9f3af4e0..b46397a452 100644 --- a/source/java/org/alfresco/repo/importer/ExportSourceImporter.java +++ b/source/java/org/alfresco/repo/importer/ExportSourceImporter.java @@ -1,260 +1,255 @@ -/* - * 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.repo.importer; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.util.List; -import java.util.Set; - -import javax.transaction.UserTransaction; - -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -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.SearchService; -import org.alfresco.service.cmr.view.ImporterBinding; -import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.cmr.view.Location; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.XMLWriter; - -public class ExportSourceImporter implements ImporterJobSPI -{ - private static Log logger = LogFactory.getLog(ExportSourceImporter.class); - - private ImporterService importerService; - - private ExportSource exportSource; - - private StoreRef storeRef; - - private String path; - - private boolean clearAllChildren; - - private NodeService nodeService; - - private SearchService searchService; - - private NamespacePrefixResolver namespacePrefixResolver; - - private TransactionService transactionService; - - private Set caches; - - public ExportSourceImporter() - { - super(); - } - - public void setImporterService(ImporterService importerService) - { - this.importerService = importerService; - } - - public void setExportSource(ExportSource exportSource) - { - this.exportSource = exportSource; - } - - public void setClearAllChildren(boolean clearAllChildren) - { - this.clearAllChildren = clearAllChildren; - } - - public void setPath(String path) - { - this.path = path; - } - - public void setStoreRef(String storeRef) - { - this.storeRef = new StoreRef(storeRef); - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) - { - this.namespacePrefixResolver = namespacePrefixResolver; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setCaches(Set caches) - { - this.caches = caches; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - logger.warn("Bearn property 'authenticationComponent' no longer used on 'ExportSourceImporter'."); - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - @SuppressWarnings("unchecked") - public void doImport() - { - UserTransaction userTransaction = null; - try - { - AuthenticationUtil.pushAuthentication(); - userTransaction = transactionService.getUserTransaction(); - userTransaction.begin(); - AuthenticationUtil.setRunAsUserSystem(); - if (clearAllChildren) - { - List refs = searchService.selectNodes(nodeService.getRootNode(storeRef), path, null, - namespacePrefixResolver, false); - for (NodeRef ref : refs) - { - for (ChildAssociationRef car : nodeService.getChildAssocs(ref)) - { - nodeService.deleteNode(car.getChildRef()); - } - } - } - - if (caches != null) - { - for (SimpleCache cache : caches) - { - - cache.clear(); - } - } - - File tempFile = TempFileProvider.createTempFile("ExportSourceImporter-", ".xml"); - Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")); - XMLWriter xmlWriter = createXMLExporter(writer); - exportSource.generateExport(xmlWriter); - xmlWriter.close(); - - Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(tempFile), "UTF-8")); - - Location location = new Location(storeRef); - location.setPath(path); - - importerService.importView(reader, location, REPLACE_BINDING, null); - reader.close(); - - if (caches != null) - { - for (SimpleCache cache : caches) - { - cache.clear(); - } - } - - userTransaction.commit(); - } - catch (Throwable t) - { - try - { - if (userTransaction != null) - { - userTransaction.rollback(); - } - } - catch (Exception ex) - { - } - throw new ExportSourceImporterException("Failed to import", t); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - } - - private XMLWriter createXMLExporter(Writer writer) - { - // Define output format - OutputFormat format = OutputFormat.createPrettyPrint(); - format.setNewLineAfterDeclaration(false); - format.setIndentSize(3); - format.setEncoding("UTF-8"); - - // Construct an XML Exporter - - XMLWriter xmlWriter = new XMLWriter(writer, format); - return xmlWriter; - } - - private static ImporterBinding REPLACE_BINDING = new ImporterBinding() - { - - public UUID_BINDING getUUIDBinding() - { - return UUID_BINDING.UPDATE_EXISTING; - } - - public String getValue(String key) - { - return null; - } - - public boolean allowReferenceWithinTransaction() - { - return false; - } - - public QName[] getExcludedClasses() - { - return null; - } - - }; - -} +/* + * 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.repo.importer; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.List; +import java.util.Set; + +import javax.transaction.UserTransaction; + +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.SearchService; +import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; + +public class ExportSourceImporter implements ImporterJobSPI +{ + private static Log logger = LogFactory.getLog(ExportSourceImporter.class); + + private ImporterService importerService; + + private ExportSource exportSource; + + private StoreRef storeRef; + + private String path; + + private boolean clearAllChildren; + + private NodeService nodeService; + + private SearchService searchService; + + private NamespacePrefixResolver namespacePrefixResolver; + + private TransactionService transactionService; + + private Set caches; + + public ExportSourceImporter() + { + super(); + } + + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + public void setExportSource(ExportSource exportSource) + { + this.exportSource = exportSource; + } + + public void setClearAllChildren(boolean clearAllChildren) + { + this.clearAllChildren = clearAllChildren; + } + + public void setPath(String path) + { + this.path = path; + } + + public void setStoreRef(String storeRef) + { + this.storeRef = new StoreRef(storeRef); + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setCaches(Set caches) + { + this.caches = caches; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + @SuppressWarnings("unchecked") + public void doImport() + { + UserTransaction userTransaction = null; + try + { + AuthenticationUtil.pushAuthentication(); + userTransaction = transactionService.getUserTransaction(); + userTransaction.begin(); + AuthenticationUtil.setRunAsUserSystem(); + if (clearAllChildren) + { + List refs = searchService.selectNodes(nodeService.getRootNode(storeRef), path, null, + namespacePrefixResolver, false); + for (NodeRef ref : refs) + { + for (ChildAssociationRef car : nodeService.getChildAssocs(ref)) + { + nodeService.deleteNode(car.getChildRef()); + } + } + } + + if (caches != null) + { + for (SimpleCache cache : caches) + { + + cache.clear(); + } + } + + File tempFile = TempFileProvider.createTempFile("ExportSourceImporter-", ".xml"); + Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")); + XMLWriter xmlWriter = createXMLExporter(writer); + exportSource.generateExport(xmlWriter); + xmlWriter.close(); + + Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(tempFile), "UTF-8")); + + Location location = new Location(storeRef); + location.setPath(path); + + importerService.importView(reader, location, REPLACE_BINDING, null); + reader.close(); + + if (caches != null) + { + for (SimpleCache cache : caches) + { + cache.clear(); + } + } + + userTransaction.commit(); + } + catch (Throwable t) + { + try + { + if (userTransaction != null) + { + userTransaction.rollback(); + } + } + catch (Exception ex) + { + } + throw new ExportSourceImporterException("Failed to import", t); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + + private XMLWriter createXMLExporter(Writer writer) + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + // Construct an XML Exporter + + XMLWriter xmlWriter = new XMLWriter(writer, format); + return xmlWriter; + } + + private static ImporterBinding REPLACE_BINDING = new ImporterBinding() + { + + public UUID_BINDING getUUIDBinding() + { + return UUID_BINDING.UPDATE_EXISTING; + } + + public String getValue(String key) + { + return null; + } + + public boolean allowReferenceWithinTransaction() + { + return false; + } + + public QName[] getExcludedClasses() + { + return null; + } + + }; + +} diff --git a/source/java/org/alfresco/repo/importer/FileSourceImporter.java b/source/java/org/alfresco/repo/importer/FileSourceImporter.java index 11cdd2228a..d1194f5299 100644 --- a/source/java/org/alfresco/repo/importer/FileSourceImporter.java +++ b/source/java/org/alfresco/repo/importer/FileSourceImporter.java @@ -1,242 +1,242 @@ -/* - * 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.repo.importer; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.Reader; -import java.util.List; -import java.util.Set; - -import javax.transaction.UserTransaction; - -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.ldap.LDAPGroupExportSource; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -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.SearchService; -import org.alfresco.service.cmr.view.ImporterBinding; -import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.cmr.view.Location; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class FileSourceImporter implements ImporterJobSPI -{ - private static Log s_logger = LogFactory.getLog(FileSourceImporter.class); - - private ImporterService importerService; - - private String fileLocation; - - private AuthenticationComponent authenticationComponent; - - private StoreRef storeRef; - - private String path; - - private boolean clearAllChildren; - - private NodeService nodeService; - - private SearchService searchService; - - private NamespacePrefixResolver namespacePrefixResolver; - - private TransactionService transactionService; - - private Set caches; - - public FileSourceImporter() - { - super(); - } - - public void setImporterService(ImporterService importerService) - { - this.importerService = importerService; - } - - public void setFileLocation(String fileLocation) - { - this.fileLocation = fileLocation; - } - - public void setClearAllChildren(boolean clearAllChildren) - { - this.clearAllChildren = clearAllChildren; - } - - public void setPath(String path) - { - this.path = path; - } - - public void setStoreRef(String storeRef) - { - this.storeRef = new StoreRef(storeRef); - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) - { - this.namespacePrefixResolver = namespacePrefixResolver; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setCaches(Set caches) - { - this.caches = caches; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - @SuppressWarnings("unchecked") - public void doImport() - { - UserTransaction userTransaction = null; - try - { - long start = System.nanoTime(); - userTransaction = transactionService.getUserTransaction(); - userTransaction.begin(); - authenticationComponent.setSystemUserAsCurrentUser(); - if (clearAllChildren) - { - List refs = searchService.selectNodes(nodeService.getRootNode(storeRef), path, null, - namespacePrefixResolver, false); - for (NodeRef ref : refs) - { - for (ChildAssociationRef car : nodeService.getChildAssocs(ref)) - { - nodeService.deleteNode(car.getChildRef()); - } - } - } - - if (caches != null) - { - for (SimpleCache cache : caches) - { - - cache.clear(); - } - } - - Reader reader = new BufferedReader(new FileReader(fileLocation)); - - Location location = new Location(storeRef); - location.setPath(path); - - importerService.importView(reader, location, REPLACE_BINDING, null); - reader.close(); - - if (caches != null) - { - for (SimpleCache cache : caches) - { - cache.clear(); - } - } - - userTransaction.commit(); - long end = System.nanoTime(); - s_logger.info("Imported "+fileLocation+ " in "+((end-start)/1e9f) + " seconds"); - } - catch (Throwable t) - { - try - { - if (userTransaction != null) - { - userTransaction.rollback(); - } - } - catch (Exception ex) - { - } - try - { - authenticationComponent.clearCurrentSecurityContext(); - } - catch (Exception ex) - { - } - throw new ExportSourceImporterException("Failed to import", t); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - } - - private static ImporterBinding REPLACE_BINDING = new ImporterBinding() - { - - public UUID_BINDING getUUIDBinding() - { - return UUID_BINDING.UPDATE_EXISTING; - } - - public String getValue(String key) - { - return null; - } - - public boolean allowReferenceWithinTransaction() - { - return false; - } - - public QName[] getExcludedClasses() - { - return null; - } - - }; - -} +/* + * 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.repo.importer; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.Reader; +import java.util.List; +import java.util.Set; + +import javax.transaction.UserTransaction; + +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.ldap.LDAPGroupExportSource; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.SearchService; +import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class FileSourceImporter implements ImporterJobSPI +{ + private static Log s_logger = LogFactory.getLog(FileSourceImporter.class); + + private ImporterService importerService; + + private String fileLocation; + + private AuthenticationContext authenticationContext; + + private StoreRef storeRef; + + private String path; + + private boolean clearAllChildren; + + private NodeService nodeService; + + private SearchService searchService; + + private NamespacePrefixResolver namespacePrefixResolver; + + private TransactionService transactionService; + + private Set caches; + + public FileSourceImporter() + { + super(); + } + + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + public void setFileLocation(String fileLocation) + { + this.fileLocation = fileLocation; + } + + public void setClearAllChildren(boolean clearAllChildren) + { + this.clearAllChildren = clearAllChildren; + } + + public void setPath(String path) + { + this.path = path; + } + + public void setStoreRef(String storeRef) + { + this.storeRef = new StoreRef(storeRef); + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setCaches(Set caches) + { + this.caches = caches; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + @SuppressWarnings("unchecked") + public void doImport() + { + UserTransaction userTransaction = null; + try + { + long start = System.nanoTime(); + userTransaction = transactionService.getUserTransaction(); + userTransaction.begin(); + authenticationContext.setSystemUserAsCurrentUser(); + if (clearAllChildren) + { + List refs = searchService.selectNodes(nodeService.getRootNode(storeRef), path, null, + namespacePrefixResolver, false); + for (NodeRef ref : refs) + { + for (ChildAssociationRef car : nodeService.getChildAssocs(ref)) + { + nodeService.deleteNode(car.getChildRef()); + } + } + } + + if (caches != null) + { + for (SimpleCache cache : caches) + { + + cache.clear(); + } + } + + Reader reader = new BufferedReader(new FileReader(fileLocation)); + + Location location = new Location(storeRef); + location.setPath(path); + + importerService.importView(reader, location, REPLACE_BINDING, null); + reader.close(); + + if (caches != null) + { + for (SimpleCache cache : caches) + { + cache.clear(); + } + } + + userTransaction.commit(); + long end = System.nanoTime(); + s_logger.info("Imported "+fileLocation+ " in "+((end-start)/1e9f) + " seconds"); + } + catch (Throwable t) + { + try + { + if (userTransaction != null) + { + userTransaction.rollback(); + } + } + catch (Exception ex) + { + } + try + { + authenticationContext.clearCurrentSecurityContext(); + } + catch (Exception ex) + { + } + throw new ExportSourceImporterException("Failed to import", t); + } + finally + { + authenticationContext.clearCurrentSecurityContext(); + } + } + + private static ImporterBinding REPLACE_BINDING = new ImporterBinding() + { + + public UUID_BINDING getUUIDBinding() + { + return UUID_BINDING.UPDATE_EXISTING; + } + + public String getValue(String key) + { + return null; + } + + public boolean allowReferenceWithinTransaction() + { + return false; + } + + public QName[] getExcludedClasses() + { + return null; + } + + }; + +} diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index fc6c121058..a365772561 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -1,718 +1,718 @@ -/* - * Copyright (C) 2005-2008 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.repo.importer; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.ResourceBundle; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.view.ImporterBinding; -import org.alfresco.service.cmr.view.ImporterException; -import org.alfresco.service.cmr.view.ImporterProgress; -import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.cmr.view.Location; -import org.alfresco.service.cmr.view.ImporterBinding.UUID_BINDING; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.AbstractLifecycleBean; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.util.FileCopyUtils; - -/** - * Bootstrap Repository store. - * - * @author David Caruana - */ -public class ImporterBootstrap extends AbstractLifecycleBean -{ - // View Properties (used in setBootstrapViews) - public static final String VIEW_PATH_PROPERTY = "path"; - public static final String VIEW_CHILDASSOCTYPE_PROPERTY = "childAssocType"; - public static final String VIEW_MESSAGES_PROPERTY = "messages"; - public static final String VIEW_LOCATION_VIEW = "location"; - public static final String VIEW_ENCODING = "encoding"; - public static final String VIEW_UUID_BINDING = "uuidBinding"; - - // Logger - private static final Log logger = LogFactory.getLog(ImporterBootstrap.class); -// private boolean logEnabled = false; - - private boolean allowWrite = true; - private boolean useExistingStore = false; - private UUID_BINDING uuidBinding = null; - // Dependencies - private TransactionService transactionService; - private NamespaceService namespaceService; - private NodeService nodeService; - private ImporterService importerService; - private List bootstrapViews; - private List extensionBootstrapViews; - private StoreRef storeRef = null; - private List mustNotExistStoreUrls = null; - private Properties configuration = null; - private String strLocale = null; - private Locale locale = null; - private AuthenticationComponent authenticationComponent; - - // Bootstrap performed? - private boolean bootstrapPerformed = false; - - - /** - * Set whether we write or not - * - * @param write true (default) if the import must go ahead, otherwise no import will occur - */ - public void setAllowWrite(boolean write) - { - this.allowWrite = write; - } - - /** - * Set whether the importer bootstrap should only perform an import if the store - * being referenced doesn't already exist. - * - * @param useExistingStore true to allow imports into an existing store, - * otherwise false (default) to only import if the store doesn't exist. - */ - public void setUseExistingStore(boolean useExistingStore) - { - this.useExistingStore = useExistingStore; - } - - /** - * Set the behaviour for generating UUIDs in the import. Values are set by the - * {@link UUID_BINDING} enum and default to {@link UUID_BINDING#CREATE_NEW_WITH_UUID}. - *

- * This setting overrides the UUID binding behaviour specified in the view properties. - * - * @param uuidBinding the UUID generation behaviour - */ - public void setUuidBinding(UUID_BINDING uuidBinding) - { - this.uuidBinding = uuidBinding; - } - - /** - * Sets the Transaction Service - * - * @param userTransaction the transaction service - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * Sets the namespace service - * - * @param namespaceService the namespace service - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Sets the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Sets the importer service - * - * @param importerService the importer service - */ - public void setImporterService(ImporterService importerService) - { - this.importerService = importerService; - } - - /** - * Set the authentication component - * - * @param authenticationComponent - */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - /** - * Sets the bootstrap views - * - * @param bootstrapViews - */ - public void setBootstrapViews(List bootstrapViews) - { - this.bootstrapViews = bootstrapViews; - } - - /** - * Sets the bootstrap views - * - * @param bootstrapViews - */ - public void addBootstrapViews(List bootstrapViews) - { - if (this.extensionBootstrapViews == null) - { - this.extensionBootstrapViews = new ArrayList(); - } - this.extensionBootstrapViews.addAll(bootstrapViews); - } - - /** - * Sets the Store Ref to bootstrap into - * - * @param storeUrl - */ - public void setStoreUrl(String storeUrl) - { - this.storeRef = new StoreRef(storeUrl); - } - - /** - * If any of the store urls exist, the bootstrap does not take place - * - * @param storeUrls the list of store urls to check - */ - public void setMustNotExistStoreUrls(List storeUrls) - { - this.mustNotExistStoreUrls = storeUrls; - } - - /** - * Gets the Store Reference - * - * @return store reference - */ - public StoreRef getStoreRef() - { - return this.storeRef; - } - - /** - * Sets the Configuration values for binding place holders - * - * @param configuration - */ - public void setConfiguration(Properties configuration) - { - this.configuration = configuration; - } - - /** - * Gets the Configuration values for binding place holders - * - * @return configuration - */ - public Properties getConfiguration() - { - return configuration; - } - - /** - * Sets the Locale - * - * @param locale (language_country_variant) - */ - public void setLocale(String locale) - { - // construct locale - this.locale = I18NUtil.parseLocale(locale); - - // store original - strLocale = locale; - } - - /** - * Get Locale - * - * @return locale - */ - public String getLocale() - { - return strLocale; - } - - /** - * @deprecated Was never used - */ - public void setLog(boolean logEnabled) - { - // Ignore - } - - /** - * Determine if bootstrap was performed? - * - * @return true => bootstrap was performed - */ - public boolean hasPerformedBootstrap() - { - return bootstrapPerformed; - } - - /** - * Bootstrap the Repository - */ - public void bootstrap() - { - PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "importerService", importerService); - - if (storeRef == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("No Store URL - bootstrap import ignored"); - } - return; - } - - try - { - // import the content - note: in MT case, this will run in System context of tenant domain - RunAsWork importRunAs = new RunAsWork() - { - public Object doWork() throws Exception - { - RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - doImport(); - return null; - } - }; - return transactionService.getRetryingTransactionHelper().doInTransaction(doImportCallback, transactionService.isReadOnly(), false); - } - }; - AuthenticationUtil.runAs(importRunAs, authenticationComponent.getSystemUserName()); - } - catch(Throwable e) - { - throw new AlfrescoRuntimeException("Bootstrap failed", e); - } - } - - /** - * Perform the actual import work. This is just separated to allow for simpler TXN demarcation. - */ - private void doImport() throws Throwable - { - // check the repository exists, create if it doesn't - if (!performBootstrap()) - { - if (logger.isDebugEnabled()) - logger.debug("Store exists - bootstrap ignored: " + storeRef); - } - else if (!allowWrite) - { - // we're in read-only node - logger.warn("Store does not exist, but mode is read-only: " + storeRef); - } - else - { - // create the store if necessary - if (!nodeService.exists(storeRef)) - { - storeRef = nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (logger.isDebugEnabled()) - logger.debug("Created store: " + storeRef); - } - - // bootstrap the store contents - if (bootstrapViews != null) - { - // add-in any extended views - if (extensionBootstrapViews != null) - { - bootstrapViews.addAll(extensionBootstrapViews); - } - - for (Properties bootstrapView : bootstrapViews) - { - String view = bootstrapView.getProperty(VIEW_LOCATION_VIEW); - if (view == null || view.length() == 0) - { - throw new ImporterException("View file location must be provided"); - } - String encoding = bootstrapView.getProperty(VIEW_ENCODING); - - // Create appropriate view reader - Reader viewReader = null; - ACPImportPackageHandler acpHandler = null; - if (view.endsWith(".acp")) - { - File viewFile = getFile(view); - acpHandler = new ACPImportPackageHandler(viewFile, encoding); - } - else - { - viewReader = getReader(view, encoding); - } - - // Create import location - Location importLocation = new Location(storeRef); - String path = bootstrapView.getProperty(VIEW_PATH_PROPERTY); - if (path != null && path.length() > 0) - { - importLocation.setPath(path); - } - String childAssocType = bootstrapView.getProperty(VIEW_CHILDASSOCTYPE_PROPERTY); - if (childAssocType != null && childAssocType.length() > 0) - { - importLocation.setChildAssocType(QName.createQName(childAssocType, namespaceService)); - } - - // Create import binding - BootstrapBinding binding = new BootstrapBinding(); - binding.setConfiguration(configuration); - binding.setLocation(importLocation); - String messages = bootstrapView.getProperty(VIEW_MESSAGES_PROPERTY); - if (messages != null && messages.length() > 0) - { - Locale bindingLocale = (locale == null) ? I18NUtil.getLocale() : locale; - ResourceBundle bundle = ResourceBundle.getBundle(messages, bindingLocale); - binding.setResourceBundle(bundle); - } - - String viewUuidBinding = bootstrapView.getProperty(VIEW_UUID_BINDING); - if (viewUuidBinding != null && viewUuidBinding.length() > 0) - { - try - { - binding.setUUIDBinding(UUID_BINDING.valueOf(UUID_BINDING.class, viewUuidBinding)); - } - catch(IllegalArgumentException e) - { - throw new ImporterException("The value " + viewUuidBinding + " is an invalid uuidBinding"); - } - } - // Override the UUID binding with the bean's - if (this.uuidBinding != null) - { - binding.setUUIDBinding(this.uuidBinding); - } - - // Now import... - ImporterProgress importProgress = null; - if (logger.isDebugEnabled()) - { - importProgress = new ImportTimerProgress(logger); - logger.debug("Importing " + view); - } - - if (viewReader != null) - { - importerService.importView(viewReader, importLocation, binding, importProgress); - } - else - { - importerService.importView(acpHandler, importLocation, binding, importProgress); - } - } - } - - // a bootstrap was performed - bootstrapPerformed = !useExistingStore; - } - } - - /** - * Get a Reader onto an XML view - * - * @param view the view location - * @param encoding the encoding of the view - * @return the reader - */ - private Reader getReader(String view, String encoding) - { - // Get Input Stream - ResourceLoader resourceLoader = new DefaultResourceLoader(getClass().getClassLoader()); - Resource viewResource = resourceLoader.getResource(view); - if (viewResource == null) - { - throw new ImporterException("Could not find view file " + view); - } - - // Wrap in buffered reader - try - { - InputStream viewStream = viewResource.getInputStream(); - InputStreamReader inputReader = (encoding == null) ? new InputStreamReader(viewStream) : new InputStreamReader(viewStream, encoding); - BufferedReader reader = new BufferedReader(inputReader); - return reader; - } - catch (UnsupportedEncodingException e) - { - throw new ImporterException("Could not create reader for view " + view + " as encoding " + encoding + " is not supported"); - } - catch (IOException e) - { - throw new ImporterException("Could not open resource for view " + view); - } - } - - /** - * Get a File representation of an XML view - * - * @param view the view location - * @return the file - */ - private File getFile(String view) - { - // Try as a file location - File file = new File(view); - if ((file != null) && (file.exists())) - { - return file; - } - else - { - // Try as a classpath location - - // Get input stream - InputStream viewStream = getClass().getClassLoader().getResourceAsStream(view); - if (viewStream == null) - { - throw new ImporterException("Could not find view file " + view); - } - - // Create output stream - File tempFile = TempFileProvider.createTempFile("acpImport", ".tmp"); - try - { - FileOutputStream os = new FileOutputStream(tempFile); - FileCopyUtils.copy(viewStream, os); - } - catch (FileNotFoundException e) - { - throw new ImporterException("Could not import view " + view, e); - } - catch (IOException e) - { - throw new ImporterException("Could not import view " + view, e); - } - return tempFile; - } - } - - - /** - * Bootstrap Binding - */ - private static class BootstrapBinding implements ImporterBinding - { - private Properties configuration = null; - private ResourceBundle resourceBundle = null; - private Location bootstrapLocation = null; - /** by default, use create new strategy for bootstrap import */ - private UUID_BINDING uuidBinding = UUID_BINDING.CREATE_NEW_WITH_UUID; - - private static final String IMPORT_LOCATION_UUID = "bootstrap.location.uuid"; - private static final String IMPORT_LOCATION_NODEREF = "bootstrap.location.noderef"; - private static final String IMPORT_LOCATION_PATH = "bootstrap.location.path"; - - /** - * Set Import Configuration - * - * @param configuration - */ - public void setConfiguration(Properties configuration) - { - this.configuration = configuration; - } - - /** - * Get Import Configuration - * - * @return configuration - */ - public Properties getConfiguration() - { - return this.configuration; - } - - /** - * Set Resource Bundle - * - * @param resourceBundle - */ - public void setResourceBundle(ResourceBundle resourceBundle) - { - this.resourceBundle = resourceBundle; - } - - /** - * Set Location - * - * @param location - */ - public void setLocation(Location location) - { - this.bootstrapLocation = location; - } - - /* (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getValue(java.lang.String) - */ - public String getValue(String key) - { - String value = null; - if (configuration != null) - { - value = configuration.getProperty(key); - } - if (value == null && resourceBundle != null) - { - value = resourceBundle.getString(key); - } - if (value == null && bootstrapLocation != null) - { - if (key.equals(IMPORT_LOCATION_UUID)) - { - value = bootstrapLocation.getNodeRef().getId(); - } - else if (key.equals(IMPORT_LOCATION_NODEREF)) - { - value = bootstrapLocation.getNodeRef().toString(); - } - else if (key.equals(IMPORT_LOCATION_PATH)) - { - value = bootstrapLocation.getPath(); - } - } - - return value; - } - - public UUID_BINDING getUUIDBinding() - { - return uuidBinding; - } - - /** - * Allow bootstrap to override default Node UUID Binding - * - * @param uuidBinding UUID_BINDING - */ - private void setUUIDBinding(UUID_BINDING uuidBinding) - { - this.uuidBinding = uuidBinding; - } - - public boolean allowReferenceWithinTransaction() - { - return true; - } - - public QName[] getExcludedClasses() - { - // Note: Do not exclude any classes, we want to import all - return new QName[] {}; - } - } - - /** - * Determine if bootstrap should take place - * - * @return true => yes, it should - */ - private boolean performBootstrap() - { - if (useExistingStore) - { - // it doesn't matter if the store exists or not - return true; - } - else if (nodeService.exists(storeRef)) - { - return false; - } - else if (mustNotExistStoreUrls != null) - { - for (String storeUrl : mustNotExistStoreUrls) - { - StoreRef storeRef = new StoreRef(storeUrl); - if (nodeService.exists(storeRef)) - { - return false; - } - } - } - - return true; - } - - @Override - protected void onBootstrap(ApplicationEvent event) - { - bootstrap(); - } - - @Override - protected void onShutdown(ApplicationEvent event) - { - // NOOP - } - -} +/* + * Copyright (C) 2005-2008 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.repo.importer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterException; +import org.alfresco.service.cmr.view.ImporterProgress; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.view.Location; +import org.alfresco.service.cmr.view.ImporterBinding.UUID_BINDING; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.FileCopyUtils; + +/** + * Bootstrap Repository store. + * + * @author David Caruana + */ +public class ImporterBootstrap extends AbstractLifecycleBean +{ + // View Properties (used in setBootstrapViews) + public static final String VIEW_PATH_PROPERTY = "path"; + public static final String VIEW_CHILDASSOCTYPE_PROPERTY = "childAssocType"; + public static final String VIEW_MESSAGES_PROPERTY = "messages"; + public static final String VIEW_LOCATION_VIEW = "location"; + public static final String VIEW_ENCODING = "encoding"; + public static final String VIEW_UUID_BINDING = "uuidBinding"; + + // Logger + private static final Log logger = LogFactory.getLog(ImporterBootstrap.class); +// private boolean logEnabled = false; + + private boolean allowWrite = true; + private boolean useExistingStore = false; + private UUID_BINDING uuidBinding = null; + // Dependencies + private TransactionService transactionService; + private NamespaceService namespaceService; + private NodeService nodeService; + private ImporterService importerService; + private List bootstrapViews; + private List extensionBootstrapViews; + private StoreRef storeRef = null; + private List mustNotExistStoreUrls = null; + private Properties configuration = null; + private String strLocale = null; + private Locale locale = null; + private AuthenticationContext authenticationContext; + + // Bootstrap performed? + private boolean bootstrapPerformed = false; + + + /** + * Set whether we write or not + * + * @param write true (default) if the import must go ahead, otherwise no import will occur + */ + public void setAllowWrite(boolean write) + { + this.allowWrite = write; + } + + /** + * Set whether the importer bootstrap should only perform an import if the store + * being referenced doesn't already exist. + * + * @param useExistingStore true to allow imports into an existing store, + * otherwise false (default) to only import if the store doesn't exist. + */ + public void setUseExistingStore(boolean useExistingStore) + { + this.useExistingStore = useExistingStore; + } + + /** + * Set the behaviour for generating UUIDs in the import. Values are set by the + * {@link UUID_BINDING} enum and default to {@link UUID_BINDING#CREATE_NEW_WITH_UUID}. + *

+ * This setting overrides the UUID binding behaviour specified in the view properties. + * + * @param uuidBinding the UUID generation behaviour + */ + public void setUuidBinding(UUID_BINDING uuidBinding) + { + this.uuidBinding = uuidBinding; + } + + /** + * Sets the Transaction Service + * + * @param userTransaction the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the namespace service + * + * @param namespaceService the namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Sets the importer service + * + * @param importerService the importer service + */ + public void setImporterService(ImporterService importerService) + { + this.importerService = importerService; + } + + /** + * Set the authentication component + * + * @param authenticationContext + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Sets the bootstrap views + * + * @param bootstrapViews + */ + public void setBootstrapViews(List bootstrapViews) + { + this.bootstrapViews = bootstrapViews; + } + + /** + * Sets the bootstrap views + * + * @param bootstrapViews + */ + public void addBootstrapViews(List bootstrapViews) + { + if (this.extensionBootstrapViews == null) + { + this.extensionBootstrapViews = new ArrayList(); + } + this.extensionBootstrapViews.addAll(bootstrapViews); + } + + /** + * Sets the Store Ref to bootstrap into + * + * @param storeUrl + */ + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + + /** + * If any of the store urls exist, the bootstrap does not take place + * + * @param storeUrls the list of store urls to check + */ + public void setMustNotExistStoreUrls(List storeUrls) + { + this.mustNotExistStoreUrls = storeUrls; + } + + /** + * Gets the Store Reference + * + * @return store reference + */ + public StoreRef getStoreRef() + { + return this.storeRef; + } + + /** + * Sets the Configuration values for binding place holders + * + * @param configuration + */ + public void setConfiguration(Properties configuration) + { + this.configuration = configuration; + } + + /** + * Gets the Configuration values for binding place holders + * + * @return configuration + */ + public Properties getConfiguration() + { + return configuration; + } + + /** + * Sets the Locale + * + * @param locale (language_country_variant) + */ + public void setLocale(String locale) + { + // construct locale + this.locale = I18NUtil.parseLocale(locale); + + // store original + strLocale = locale; + } + + /** + * Get Locale + * + * @return locale + */ + public String getLocale() + { + return strLocale; + } + + /** + * @deprecated Was never used + */ + public void setLog(boolean logEnabled) + { + // Ignore + } + + /** + * Determine if bootstrap was performed? + * + * @return true => bootstrap was performed + */ + public boolean hasPerformedBootstrap() + { + return bootstrapPerformed; + } + + /** + * Bootstrap the Repository + */ + public void bootstrap() + { + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "importerService", importerService); + + if (storeRef == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No Store URL - bootstrap import ignored"); + } + return; + } + + try + { + // import the content - note: in MT case, this will run in System context of tenant domain + RunAsWork importRunAs = new RunAsWork() + { + public Object doWork() throws Exception + { + RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + doImport(); + return null; + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(doImportCallback, transactionService.isReadOnly(), false); + } + }; + AuthenticationUtil.runAs(importRunAs, authenticationContext.getSystemUserName()); + } + catch(Throwable e) + { + throw new AlfrescoRuntimeException("Bootstrap failed", e); + } + } + + /** + * Perform the actual import work. This is just separated to allow for simpler TXN demarcation. + */ + private void doImport() throws Throwable + { + // check the repository exists, create if it doesn't + if (!performBootstrap()) + { + if (logger.isDebugEnabled()) + logger.debug("Store exists - bootstrap ignored: " + storeRef); + } + else if (!allowWrite) + { + // we're in read-only node + logger.warn("Store does not exist, but mode is read-only: " + storeRef); + } + else + { + // create the store if necessary + if (!nodeService.exists(storeRef)) + { + storeRef = nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (logger.isDebugEnabled()) + logger.debug("Created store: " + storeRef); + } + + // bootstrap the store contents + if (bootstrapViews != null) + { + // add-in any extended views + if (extensionBootstrapViews != null) + { + bootstrapViews.addAll(extensionBootstrapViews); + } + + for (Properties bootstrapView : bootstrapViews) + { + String view = bootstrapView.getProperty(VIEW_LOCATION_VIEW); + if (view == null || view.length() == 0) + { + throw new ImporterException("View file location must be provided"); + } + String encoding = bootstrapView.getProperty(VIEW_ENCODING); + + // Create appropriate view reader + Reader viewReader = null; + ACPImportPackageHandler acpHandler = null; + if (view.endsWith(".acp")) + { + File viewFile = getFile(view); + acpHandler = new ACPImportPackageHandler(viewFile, encoding); + } + else + { + viewReader = getReader(view, encoding); + } + + // Create import location + Location importLocation = new Location(storeRef); + String path = bootstrapView.getProperty(VIEW_PATH_PROPERTY); + if (path != null && path.length() > 0) + { + importLocation.setPath(path); + } + String childAssocType = bootstrapView.getProperty(VIEW_CHILDASSOCTYPE_PROPERTY); + if (childAssocType != null && childAssocType.length() > 0) + { + importLocation.setChildAssocType(QName.createQName(childAssocType, namespaceService)); + } + + // Create import binding + BootstrapBinding binding = new BootstrapBinding(); + binding.setConfiguration(configuration); + binding.setLocation(importLocation); + String messages = bootstrapView.getProperty(VIEW_MESSAGES_PROPERTY); + if (messages != null && messages.length() > 0) + { + Locale bindingLocale = (locale == null) ? I18NUtil.getLocale() : locale; + ResourceBundle bundle = ResourceBundle.getBundle(messages, bindingLocale); + binding.setResourceBundle(bundle); + } + + String viewUuidBinding = bootstrapView.getProperty(VIEW_UUID_BINDING); + if (viewUuidBinding != null && viewUuidBinding.length() > 0) + { + try + { + binding.setUUIDBinding(UUID_BINDING.valueOf(UUID_BINDING.class, viewUuidBinding)); + } + catch(IllegalArgumentException e) + { + throw new ImporterException("The value " + viewUuidBinding + " is an invalid uuidBinding"); + } + } + // Override the UUID binding with the bean's + if (this.uuidBinding != null) + { + binding.setUUIDBinding(this.uuidBinding); + } + + // Now import... + ImporterProgress importProgress = null; + if (logger.isDebugEnabled()) + { + importProgress = new ImportTimerProgress(logger); + logger.debug("Importing " + view); + } + + if (viewReader != null) + { + importerService.importView(viewReader, importLocation, binding, importProgress); + } + else + { + importerService.importView(acpHandler, importLocation, binding, importProgress); + } + } + } + + // a bootstrap was performed + bootstrapPerformed = !useExistingStore; + } + } + + /** + * Get a Reader onto an XML view + * + * @param view the view location + * @param encoding the encoding of the view + * @return the reader + */ + private Reader getReader(String view, String encoding) + { + // Get Input Stream + ResourceLoader resourceLoader = new DefaultResourceLoader(getClass().getClassLoader()); + Resource viewResource = resourceLoader.getResource(view); + if (viewResource == null) + { + throw new ImporterException("Could not find view file " + view); + } + + // Wrap in buffered reader + try + { + InputStream viewStream = viewResource.getInputStream(); + InputStreamReader inputReader = (encoding == null) ? new InputStreamReader(viewStream) : new InputStreamReader(viewStream, encoding); + BufferedReader reader = new BufferedReader(inputReader); + return reader; + } + catch (UnsupportedEncodingException e) + { + throw new ImporterException("Could not create reader for view " + view + " as encoding " + encoding + " is not supported"); + } + catch (IOException e) + { + throw new ImporterException("Could not open resource for view " + view); + } + } + + /** + * Get a File representation of an XML view + * + * @param view the view location + * @return the file + */ + private File getFile(String view) + { + // Try as a file location + File file = new File(view); + if ((file != null) && (file.exists())) + { + return file; + } + else + { + // Try as a classpath location + + // Get input stream + InputStream viewStream = getClass().getClassLoader().getResourceAsStream(view); + if (viewStream == null) + { + throw new ImporterException("Could not find view file " + view); + } + + // Create output stream + File tempFile = TempFileProvider.createTempFile("acpImport", ".tmp"); + try + { + FileOutputStream os = new FileOutputStream(tempFile); + FileCopyUtils.copy(viewStream, os); + } + catch (FileNotFoundException e) + { + throw new ImporterException("Could not import view " + view, e); + } + catch (IOException e) + { + throw new ImporterException("Could not import view " + view, e); + } + return tempFile; + } + } + + + /** + * Bootstrap Binding + */ + private static class BootstrapBinding implements ImporterBinding + { + private Properties configuration = null; + private ResourceBundle resourceBundle = null; + private Location bootstrapLocation = null; + /** by default, use create new strategy for bootstrap import */ + private UUID_BINDING uuidBinding = UUID_BINDING.CREATE_NEW_WITH_UUID; + + private static final String IMPORT_LOCATION_UUID = "bootstrap.location.uuid"; + private static final String IMPORT_LOCATION_NODEREF = "bootstrap.location.noderef"; + private static final String IMPORT_LOCATION_PATH = "bootstrap.location.path"; + + /** + * Set Import Configuration + * + * @param configuration + */ + public void setConfiguration(Properties configuration) + { + this.configuration = configuration; + } + + /** + * Get Import Configuration + * + * @return configuration + */ + public Properties getConfiguration() + { + return this.configuration; + } + + /** + * Set Resource Bundle + * + * @param resourceBundle + */ + public void setResourceBundle(ResourceBundle resourceBundle) + { + this.resourceBundle = resourceBundle; + } + + /** + * Set Location + * + * @param location + */ + public void setLocation(Location location) + { + this.bootstrapLocation = location; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.view.ImporterBinding#getValue(java.lang.String) + */ + public String getValue(String key) + { + String value = null; + if (configuration != null) + { + value = configuration.getProperty(key); + } + if (value == null && resourceBundle != null) + { + value = resourceBundle.getString(key); + } + if (value == null && bootstrapLocation != null) + { + if (key.equals(IMPORT_LOCATION_UUID)) + { + value = bootstrapLocation.getNodeRef().getId(); + } + else if (key.equals(IMPORT_LOCATION_NODEREF)) + { + value = bootstrapLocation.getNodeRef().toString(); + } + else if (key.equals(IMPORT_LOCATION_PATH)) + { + value = bootstrapLocation.getPath(); + } + } + + return value; + } + + public UUID_BINDING getUUIDBinding() + { + return uuidBinding; + } + + /** + * Allow bootstrap to override default Node UUID Binding + * + * @param uuidBinding UUID_BINDING + */ + private void setUUIDBinding(UUID_BINDING uuidBinding) + { + this.uuidBinding = uuidBinding; + } + + public boolean allowReferenceWithinTransaction() + { + return true; + } + + public QName[] getExcludedClasses() + { + // Note: Do not exclude any classes, we want to import all + return new QName[] {}; + } + } + + /** + * Determine if bootstrap should take place + * + * @return true => yes, it should + */ + private boolean performBootstrap() + { + if (useExistingStore) + { + // it doesn't matter if the store exists or not + return true; + } + else if (nodeService.exists(storeRef)) + { + return false; + } + else if (mustNotExistStoreUrls != null) + { + for (String storeUrl : mustNotExistStoreUrls) + { + StoreRef storeRef = new StoreRef(storeUrl); + if (nodeService.exists(storeRef)) + { + return false; + } + } + } + + return true; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } + +} diff --git a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java index dcef65ac15..e0a883ca17 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java +++ b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java @@ -29,7 +29,7 @@ import java.util.List; import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.view.ImporterException; @@ -48,7 +48,7 @@ public class SystemInfoBootstrap extends AbstractLifecycleBean // dependencies private TransactionService transactionService; private NodeService nodeService; - private AuthenticationComponent authenticationComponent; + private AuthenticationContext authenticationContext; private SystemExporterImporter systemImporter; private List mustNotExistStoreUrls = null; @@ -78,11 +78,11 @@ public class SystemInfoBootstrap extends AbstractLifecycleBean /** * Set the authentication component * - * @param authenticationComponent + * @param authenticationContext */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + public void setAuthenticationContext(AuthenticationContext authenticationContext) { - this.authenticationComponent = authenticationComponent; + this.authenticationContext = authenticationContext; } /** @@ -121,7 +121,7 @@ public class SystemInfoBootstrap extends AbstractLifecycleBean public void bootstrap() { UserTransaction userTransaction = transactionService.getUserTransaction(); - authenticationComponent.setSystemUserAsCurrentUser(); + authenticationContext.setSystemUserAsCurrentUser(); try { @@ -150,12 +150,12 @@ public class SystemInfoBootstrap extends AbstractLifecycleBean { // rollback the transaction try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + try {authenticationContext.clearCurrentSecurityContext(); } catch (Exception ex) {} throw new AlfrescoRuntimeException("System Info Bootstrap failed", e); } finally { - authenticationComponent.clearCurrentSecurityContext(); + authenticationContext.clearCurrentSecurityContext(); } } diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java index 543b7aa7b1..d3f39104af 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -1,662 +1,661 @@ -/* - * Copyright (C) 2005-2008 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.repo.module; - -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.admin.registry.RegistryKey; -import org.alfresco.repo.admin.registry.RegistryService; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.Tenant; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.module.ModuleDependency; -import org.alfresco.service.cmr.module.ModuleDetails; -import org.alfresco.service.cmr.module.ModuleService; -import org.alfresco.service.descriptor.DescriptorService; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.VersionNumber; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Helper class to split up some of the code for managing module components. This class handles - * the execution of the module components. - * - * @author Derek Hulley - */ -public class ModuleComponentHelper -{ - public static final String URI_MODULES_1_0 = "http://www.alfresco.org/system/modules/1.0"; - private static final String REGISTRY_PATH_MODULES = "modules"; - private static final String REGISTRY_PROPERTY_INSTALLED_VERSION = "installedVersion"; - private static final String REGISTRY_PROPERTY_CURRENT_VERSION = "currentVersion"; - private static final String REGISTRY_PATH_COMPONENTS = "components"; - private static final String REGISTRY_PROPERTY_EXECUTION_DATE = "executionDate"; - - private static final String MSG_FOUND_MODULES = "module.msg.found_modules"; - private static final String MSG_STARTING = "module.msg.starting"; - private static final String MSG_INSTALLING = "module.msg.installing"; - private static final String MSG_UPGRADING = "module.msg.upgrading"; - private static final String MSG_DEPENDENCIES = "module.msg.dependencies"; - private static final String MSG_MISSING = "module.msg.missing"; - private static final String WARN_NO_INSTALL_VERSION = "module.warn.no_install_version"; - private static final String ERR_MISSING_DEPENDENCY = "module.err.missing_dependency"; - private static final String ERR_UNSUPPORTED_REPO_VERSION = "module.err.unsupported_repo_version"; - private static final String ERR_NO_DOWNGRADE = "module.err.downgrading_not_supported"; - private static final String ERR_COMPONENT_ALREADY_REGISTERED = "module.err.component_already_registered"; - private static final String ERR_COMPONENT_IN_MISSING_MODULE = "module.err.component_in_missing_module"; - private static final String ERR_ORPHANED_COMPONENTS = "module.err.orphaned_components"; - - private static Log logger = LogFactory.getLog(ModuleComponentHelper.class); - private static Log loggerService = LogFactory.getLog(ModuleServiceImpl.class); - - private ServiceRegistry serviceRegistry; - private DescriptorService descriptorService; - private RegistryService registryService; - private ModuleService moduleService; - private TenantAdminService tenantAdminService; - private Map> componentsByNameByModule; - - /** Default constructor */ - public ModuleComponentHelper() - { - componentsByNameByModule = new HashMap>(7); - } - - /** - * @param serviceRegistry provides access to the service APIs - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - } - - /** - * @param descriptorService gives access to the current repository version - */ - public void setDescriptorService(DescriptorService descriptorService) - { - this.descriptorService = descriptorService; - } - - /** - * @param registryService the service used to persist component execution details. - */ - public void setRegistryService(RegistryService registryService) - { - this.registryService = registryService; - } - - /** - * @param moduleService the service from which to get the available modules. - */ - public void setModuleService(ModuleService moduleService) - { - this.moduleService = moduleService; - } - - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - /** - * Add a managed module component to the registry of components. These will be controlled - * by the {@link #startModules()} method. - * - * @param component a module component to be executed - */ - public synchronized void registerComponent(ModuleComponent component) - { - String moduleId = component.getModuleId(); - String name = component.getName(); - // Get the map of components for the module - Map componentsByName = componentsByNameByModule.get(moduleId); - if (componentsByName == null) - { - componentsByName = new HashMap(11); - componentsByNameByModule.put(moduleId, componentsByName); - } - // Check if the component has already been registered - if (componentsByName.containsKey(name)) - { - throw AlfrescoRuntimeException.create(ERR_COMPONENT_ALREADY_REGISTERED, name, moduleId); - } - // Add it - componentsByName.put(name, component); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Registered component: " + component); - } - } - - /** - * @return Returns the map of components keyed by name. The map could be empty but - * will never be null. - */ - private synchronized Map getComponents(String moduleId) - { - Map componentsByName = componentsByNameByModule.get(moduleId); - if (componentsByName != null) - { - // Done - return componentsByName; - } - else - { - // Done - return Collections.emptyMap(); - } - } - - /** - * {@inheritDoc} - */ - public synchronized void startModules() - { - // Check properties - PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); -// PropertyCheck.mandatory(this, "authenticationComponent", authenticationComponent); - PropertyCheck.mandatory(this, "registryService", registryService); - PropertyCheck.mandatory(this, "moduleService", moduleService); - PropertyCheck.mandatory(this, "tenantAdminService", tenantAdminService); - /* - * Ensure transactionality and the correct authentication - */ - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - try - { - TransactionService transactionService = serviceRegistry.getTransactionService(); - - // Get all the modules - List modules = moduleService.getAllModules(); - loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); - - // Process each module in turn. Ordering is not important. - final Map> mapExecutedComponents = new HashMap>(1); - final Map> mapStartedModules = new HashMap>(1); - - // Note: for system bootstrap this will be the default domain, else tenant domain for tenant create/import - final String tenantDomainCtx = tenantAdminService.getCurrentUserDomain(); - - mapExecutedComponents.put(tenantDomainCtx, new HashSet(10)); - mapStartedModules.put(tenantDomainCtx, new HashSet(2)); - - final List tenants; - if (tenantAdminService.isEnabled() && (tenantDomainCtx.equals(TenantService.DEFAULT_DOMAIN))) - { - tenants = tenantAdminService.getAllTenants(); - for (Tenant tenant : tenants) - { - mapExecutedComponents.put(tenant.getTenantDomain(), new HashSet(10)); - mapStartedModules.put(tenant.getTenantDomain(), new HashSet(2)); - } - } - else - { - tenants = null; - } - - for (final ModuleDetails module : modules) - { - RetryingTransactionCallback startModuleWork = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - startModule(module, mapStartedModules.get(tenantDomainCtx), mapExecutedComponents.get(tenantDomainCtx)); - - if (tenants != null) - { - for (Tenant tenant : tenants) - { - final String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - startModule(module, mapStartedModules.get(tenantDomain), mapExecutedComponents.get(tenantDomain)); - return null; - } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - } - } - - return null; - } - }; - transactionService.getRetryingTransactionHelper().doInTransaction(startModuleWork); - } - - // Check for missing modules. - checkForMissingModules(); - - if (tenants != null) - { - for (Tenant tenant : tenants) - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - checkForMissingModules(); - return null; - } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); - } - } - - // Check that all components where executed, or considered for execution - checkForOrphanComponents(mapExecutedComponents.get(tenantDomainCtx)); - - if (tenants != null) - { - for (Tenant tenant : tenants) - { - final String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - checkForOrphanComponents(mapExecutedComponents.get(tenantDomain)); - return null; - } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - } - } - } - catch (Throwable e) - { - throw new AlfrescoRuntimeException("Failed to start modules", e); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - } - - /** - * Checks that all components have been executed or considered for execution. - * @param executedComponents - */ - private void checkForOrphanComponents(Set executedComponents) - { - Set missedComponents = new HashSet(executedComponents); - - // Iterate over each module registered by components - for (Map.Entry> entry : componentsByNameByModule.entrySet()) - { - String moduleId = entry.getKey(); - Map componentsByName = entry.getValue(); - // Iterate over each component registered against the module ID - for (Map.Entry entryInner : componentsByName.entrySet()) - { - String componentName = entryInner.getKey(); - ModuleComponent component = entryInner.getValue(); - // Check if it has been executed - if (executedComponents.contains(component)) - { - // It was executed, so remove it from the missed components set - missedComponents.remove(component); - } - else - { - String msg = I18NUtil.getMessage( - ERR_COMPONENT_IN_MISSING_MODULE, - componentName, moduleId); - logger.error(msg); - } - } - } - // Dump if there were orphans - if (missedComponents.size() > 0) - { - throw AlfrescoRuntimeException.create(ERR_ORPHANED_COMPONENTS, missedComponents.size()); - } - } - - /** - * Checks to see if there are any modules registered as installed that aren't in the - * list of modules taken from the WAR. - *

- * Currently, the behaviour specified is that a warning is generated only. - */ - private void checkForMissingModules() - { - // Get the IDs of all modules from the registry - RegistryKey moduleKeyAllIds = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, null); - Collection moduleIds = registryService.getChildElements(moduleKeyAllIds); - - // Check that each module is present in the distribution - for (String moduleId : moduleIds) - { - ModuleDetails moduleDetails = moduleService.getModule(moduleId); - if (moduleDetails != null) - { - if (logger.isDebugEnabled()) - { - logger.debug("Installed module found in distribution: " + moduleId); - } - } - else - { - // Get the specifics of the missing module - RegistryKey moduleKeyCurrentVersion = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); - VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); - // The module is missing, so warn - loggerService.warn(I18NUtil.getMessage(MSG_MISSING, moduleId, versionCurrent)); - } - } - } - - /** - * Copies, where necessary, the module registry details from the alias details - * and removes the alias details. - */ - private void renameModule(ModuleDetails module) - { - String moduleId = module.getId(); - List moduleAliases = module.getAliases(); - - // Get the IDs of all modules from the registry - RegistryKey moduleKeyAllIds = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, null); - Collection registeredModuleIds = registryService.getChildElements(moduleKeyAllIds); - - // Firstly, is the module installed? - if (registeredModuleIds.contains(moduleId)) - { - // It is there, so we do nothing - return; - } - // Check if any of the registered modules are on the alias list - for (String moduleAlias : moduleAliases) - { - // Is this alias registered? - if (!registeredModuleIds.contains(moduleAlias)) - { - // No alias registered - continue; - } - // We found an alias and have to rename it to the new module ID - RegistryKey moduleKeyNew = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, null); - RegistryKey moduleKeyOld = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleAlias, null); - // Copy it all - registryService.copy(moduleKeyOld, moduleKeyNew); - // Remove the source - registryService.delete(moduleKeyOld); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Moved old module alias to new module ID: \n" + - " Alias: " + moduleAlias + "\n" + - " Module: " + moduleId); - } - break; - } - } - - /** - * Does the actual work without fussing about transactions and authentication. - * Module dependencies will be started first, but a module will only be started - * once. - * - * @param module the module to start - * @param startedModules the IDs of modules that have already started - * @param executedComponents keep track of the executed components - */ - private void startModule(ModuleDetails module, Set startedModules, Set executedComponents) - { - String moduleId = module.getId(); - VersionNumber moduleNewVersion = module.getVersion(); - - // Double check whether we have done this module already - if (startedModules.contains(moduleId)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Module '" + module + "' already started"); - } - return; - } - - // Start dependencies - List moduleDependencies = module.getDependencies(); - for (ModuleDependency moduleDependency : moduleDependencies) - { - if (logger.isDebugEnabled()) - { - logger.debug("Module '" + module + "' depends on: " + moduleDependency); - } - // Get the dependency - String moduleDependencyId = moduleDependency.getDependencyId(); - ModuleDetails moduleDependencyDetails = moduleService.getModule(moduleDependencyId); - // Check that it is there - if (moduleDependencyDetails == null) - { - // The dependency is not there - // List required dependencies - StringBuilder sb = new StringBuilder(128); - for (ModuleDependency dependency : moduleDependencies) - { - sb.append("\n").append(dependency); - } - String msg = I18NUtil.getMessage( - MSG_DEPENDENCIES, - moduleId, moduleNewVersion, sb.toString()); - logger.info(msg); - // Now fail - throw AlfrescoRuntimeException.create( - ERR_MISSING_DEPENDENCY, - moduleId, moduleNewVersion, moduleDependency); - } - // The dependency is installed, so start it - startModule(moduleDependencyDetails, startedModules, executedComponents); - } - - // Check if the module needs a rename first - renameModule(module); - - // First check that the module version is fundamentally compatible with the repository - VersionNumber repoVersionNumber = descriptorService.getServerDescriptor().getVersionNumber(); - VersionNumber minRepoVersionNumber = module.getRepoVersionMin(); - VersionNumber maxRepoVersionNumber = module.getRepoVersionMax(); - if ((minRepoVersionNumber != null && repoVersionNumber.compareTo(minRepoVersionNumber) < 0) || - (maxRepoVersionNumber != null && repoVersionNumber.compareTo(maxRepoVersionNumber) > 0)) - { - // The current repo version is not supported - throw AlfrescoRuntimeException.create( - ERR_UNSUPPORTED_REPO_VERSION, - moduleId, moduleNewVersion, repoVersionNumber, minRepoVersionNumber, maxRepoVersionNumber); - } - - // Get the module details from the registry - RegistryKey moduleKeyInstalledVersion = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_INSTALLED_VERSION); - RegistryKey moduleKeyCurrentVersion = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); - VersionNumber moduleInstallVersion = (VersionNumber) registryService.getProperty(moduleKeyInstalledVersion); - VersionNumber moduleCurrentVersion = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); - String msg = null; - if (moduleCurrentVersion == null) // No previous record of it - { - msg = I18NUtil.getMessage(MSG_INSTALLING, moduleId, moduleNewVersion); - // Record the install version - registryService.addProperty(moduleKeyInstalledVersion, moduleNewVersion); - moduleInstallVersion = moduleNewVersion; - moduleCurrentVersion = moduleNewVersion; - } - else // It is an upgrade or is the same - { - // Check that we have an installed version - if (moduleInstallVersion == null) - { - // A current version, but no installed version - logger.warn(I18NUtil.getMessage(WARN_NO_INSTALL_VERSION, moduleId, moduleCurrentVersion)); - // Record the install version - registryService.addProperty(moduleKeyInstalledVersion, moduleCurrentVersion); - moduleInstallVersion = moduleCurrentVersion; - } - - if (moduleCurrentVersion.compareTo(moduleNewVersion) == 0) // The current version is the same - { - msg = I18NUtil.getMessage(MSG_STARTING, moduleId, moduleNewVersion); - } - else if (moduleCurrentVersion.compareTo(moduleNewVersion) > 0) // Downgrading not supported - { - throw AlfrescoRuntimeException.create(ERR_NO_DOWNGRADE, moduleId, moduleCurrentVersion, moduleNewVersion); - } - else // This is an upgrade - { - msg = I18NUtil.getMessage(MSG_UPGRADING, moduleId, moduleNewVersion, moduleCurrentVersion); - } - } - loggerService.info(msg); - // Record the current version - registryService.addProperty(moduleKeyCurrentVersion, moduleNewVersion); - - Map componentsByName = getComponents(moduleId); - for (ModuleComponent component : componentsByName.values()) - { - executeComponent(moduleId, moduleInstallVersion, component, executedComponents); - } - - // Keep track of the ID as it started successfully - startedModules.add(moduleId); - - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Started module '" + module + "' including " + executedComponents.size() + "components."); - } - } - - /** - * Execute the component, respecting dependencies. - */ - private void executeComponent( - String moduleId, - VersionNumber moduleInstallVersion, - ModuleComponent component, - Set executedComponents) - { - // Ignore if it has been executed in this run already - if (executedComponents.contains(component)) - { - // Already done - if (logger.isDebugEnabled()) - { - logger.debug("Skipping component already executed in this run: \n" + - " Component: " + component); - } - return; - } - // Keep track of the fact that we considered it for execution - executedComponents.add(component); - - // Check the version applicability - VersionNumber minVersion = component.getAppliesFromVersionNumber(); - VersionNumber maxVersion = component.getAppliesToVersionNumber(); - if (moduleInstallVersion.compareTo(minVersion) < 0 || moduleInstallVersion.compareTo(maxVersion) > 0) - { - // It is out of the allowable range for execution so we just ignore it - if (logger.isDebugEnabled()) - { - logger.debug("Skipping component that doesn't apply to the module installation version: \n" + - " Component: " + component + "\n" + - " Module: " + moduleId + "\n" + - " Install Version: " + moduleInstallVersion + "\n" + - " Applies From : " + minVersion + "\n" + - " Applies To : " + maxVersion); - } - return; - } - - // Construct the registry key to store the execution date - String name = component.getName(); - RegistryKey executionDateKey = new RegistryKey( - ModuleComponentHelper.URI_MODULES_1_0, - REGISTRY_PATH_MODULES, moduleId, REGISTRY_PATH_COMPONENTS, name, REGISTRY_PROPERTY_EXECUTION_DATE); - - // Check if the component has been executed - Date executionDate = (Date) registryService.getProperty(executionDateKey); - if (executionDate != null && component.isExecuteOnceOnly()) - { - // It has been executed and is scheduled for a single execution - leave it - if (logger.isDebugEnabled()) - { - logger.debug("Skipping already-executed module component: \n" + - " Component: " + component + "\n" + - " Execution Time: " + executionDate); - } - return; - } - // It may have been executed, but not in this run and it is allowed to be repeated - // Check for dependencies - List dependencies = component.getDependsOn(); - for (ModuleComponent dependency : dependencies) - { - executeComponent(moduleId, moduleInstallVersion, dependency, executedComponents); - } - // Execute the component itself - component.execute(); - // Keep track of it in the registry - registryService.addProperty(executionDateKey, new Date()); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Executed module component: \n" + - " Component: " + component); - } - } -} +/* + * Copyright (C) 2005-2008 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.repo.module; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.registry.RegistryKey; +import org.alfresco.repo.admin.registry.RegistryService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.Tenant; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleDependency; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VersionNumber; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Helper class to split up some of the code for managing module components. This class handles + * the execution of the module components. + * + * @author Derek Hulley + */ +public class ModuleComponentHelper +{ + public static final String URI_MODULES_1_0 = "http://www.alfresco.org/system/modules/1.0"; + private static final String REGISTRY_PATH_MODULES = "modules"; + private static final String REGISTRY_PROPERTY_INSTALLED_VERSION = "installedVersion"; + private static final String REGISTRY_PROPERTY_CURRENT_VERSION = "currentVersion"; + private static final String REGISTRY_PATH_COMPONENTS = "components"; + private static final String REGISTRY_PROPERTY_EXECUTION_DATE = "executionDate"; + + private static final String MSG_FOUND_MODULES = "module.msg.found_modules"; + private static final String MSG_STARTING = "module.msg.starting"; + private static final String MSG_INSTALLING = "module.msg.installing"; + private static final String MSG_UPGRADING = "module.msg.upgrading"; + private static final String MSG_DEPENDENCIES = "module.msg.dependencies"; + private static final String MSG_MISSING = "module.msg.missing"; + private static final String WARN_NO_INSTALL_VERSION = "module.warn.no_install_version"; + private static final String ERR_MISSING_DEPENDENCY = "module.err.missing_dependency"; + private static final String ERR_UNSUPPORTED_REPO_VERSION = "module.err.unsupported_repo_version"; + private static final String ERR_NO_DOWNGRADE = "module.err.downgrading_not_supported"; + private static final String ERR_COMPONENT_ALREADY_REGISTERED = "module.err.component_already_registered"; + private static final String ERR_COMPONENT_IN_MISSING_MODULE = "module.err.component_in_missing_module"; + private static final String ERR_ORPHANED_COMPONENTS = "module.err.orphaned_components"; + + private static Log logger = LogFactory.getLog(ModuleComponentHelper.class); + private static Log loggerService = LogFactory.getLog(ModuleServiceImpl.class); + + private ServiceRegistry serviceRegistry; + private DescriptorService descriptorService; + private RegistryService registryService; + private ModuleService moduleService; + private TenantAdminService tenantAdminService; + private Map> componentsByNameByModule; + + /** Default constructor */ + public ModuleComponentHelper() + { + componentsByNameByModule = new HashMap>(7); + } + + /** + * @param serviceRegistry provides access to the service APIs + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param descriptorService gives access to the current repository version + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + /** + * @param registryService the service used to persist component execution details. + */ + public void setRegistryService(RegistryService registryService) + { + this.registryService = registryService; + } + + /** + * @param moduleService the service from which to get the available modules. + */ + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + /** + * Add a managed module component to the registry of components. These will be controlled + * by the {@link #startModules()} method. + * + * @param component a module component to be executed + */ + public synchronized void registerComponent(ModuleComponent component) + { + String moduleId = component.getModuleId(); + String name = component.getName(); + // Get the map of components for the module + Map componentsByName = componentsByNameByModule.get(moduleId); + if (componentsByName == null) + { + componentsByName = new HashMap(11); + componentsByNameByModule.put(moduleId, componentsByName); + } + // Check if the component has already been registered + if (componentsByName.containsKey(name)) + { + throw AlfrescoRuntimeException.create(ERR_COMPONENT_ALREADY_REGISTERED, name, moduleId); + } + // Add it + componentsByName.put(name, component); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Registered component: " + component); + } + } + + /** + * @return Returns the map of components keyed by name. The map could be empty but + * will never be null. + */ + private synchronized Map getComponents(String moduleId) + { + Map componentsByName = componentsByNameByModule.get(moduleId); + if (componentsByName != null) + { + // Done + return componentsByName; + } + else + { + // Done + return Collections.emptyMap(); + } + } + + /** + * {@inheritDoc} + */ + public synchronized void startModules() + { + // Check properties + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "registryService", registryService); + PropertyCheck.mandatory(this, "moduleService", moduleService); + PropertyCheck.mandatory(this, "tenantAdminService", tenantAdminService); + /* + * Ensure transactionality and the correct authentication + */ + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + try + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + + // Get all the modules + List modules = moduleService.getAllModules(); + loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); + + // Process each module in turn. Ordering is not important. + final Map> mapExecutedComponents = new HashMap>(1); + final Map> mapStartedModules = new HashMap>(1); + + // Note: for system bootstrap this will be the default domain, else tenant domain for tenant create/import + final String tenantDomainCtx = tenantAdminService.getCurrentUserDomain(); + + mapExecutedComponents.put(tenantDomainCtx, new HashSet(10)); + mapStartedModules.put(tenantDomainCtx, new HashSet(2)); + + final List tenants; + if (tenantAdminService.isEnabled() && (tenantDomainCtx.equals(TenantService.DEFAULT_DOMAIN))) + { + tenants = tenantAdminService.getAllTenants(); + for (Tenant tenant : tenants) + { + mapExecutedComponents.put(tenant.getTenantDomain(), new HashSet(10)); + mapStartedModules.put(tenant.getTenantDomain(), new HashSet(2)); + } + } + else + { + tenants = null; + } + + for (final ModuleDetails module : modules) + { + RetryingTransactionCallback startModuleWork = new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + startModule(module, mapStartedModules.get(tenantDomainCtx), mapExecutedComponents.get(tenantDomainCtx)); + + if (tenants != null) + { + for (Tenant tenant : tenants) + { + final String tenantDomain = tenant.getTenantDomain(); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + startModule(module, mapStartedModules.get(tenantDomain), mapExecutedComponents.get(tenantDomain)); + return null; + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + } + + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(startModuleWork); + } + + // Check for missing modules. + checkForMissingModules(); + + if (tenants != null) + { + for (Tenant tenant : tenants) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + checkForMissingModules(); + return null; + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + } + } + + // Check that all components where executed, or considered for execution + checkForOrphanComponents(mapExecutedComponents.get(tenantDomainCtx)); + + if (tenants != null) + { + for (Tenant tenant : tenants) + { + final String tenantDomain = tenant.getTenantDomain(); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + checkForOrphanComponents(mapExecutedComponents.get(tenantDomain)); + return null; + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + } + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to start modules", e); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + /** + * Checks that all components have been executed or considered for execution. + * @param executedComponents + */ + private void checkForOrphanComponents(Set executedComponents) + { + Set missedComponents = new HashSet(executedComponents); + + // Iterate over each module registered by components + for (Map.Entry> entry : componentsByNameByModule.entrySet()) + { + String moduleId = entry.getKey(); + Map componentsByName = entry.getValue(); + // Iterate over each component registered against the module ID + for (Map.Entry entryInner : componentsByName.entrySet()) + { + String componentName = entryInner.getKey(); + ModuleComponent component = entryInner.getValue(); + // Check if it has been executed + if (executedComponents.contains(component)) + { + // It was executed, so remove it from the missed components set + missedComponents.remove(component); + } + else + { + String msg = I18NUtil.getMessage( + ERR_COMPONENT_IN_MISSING_MODULE, + componentName, moduleId); + logger.error(msg); + } + } + } + // Dump if there were orphans + if (missedComponents.size() > 0) + { + throw AlfrescoRuntimeException.create(ERR_ORPHANED_COMPONENTS, missedComponents.size()); + } + } + + /** + * Checks to see if there are any modules registered as installed that aren't in the + * list of modules taken from the WAR. + *

+ * Currently, the behaviour specified is that a warning is generated only. + */ + private void checkForMissingModules() + { + // Get the IDs of all modules from the registry + RegistryKey moduleKeyAllIds = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, null); + Collection moduleIds = registryService.getChildElements(moduleKeyAllIds); + + // Check that each module is present in the distribution + for (String moduleId : moduleIds) + { + ModuleDetails moduleDetails = moduleService.getModule(moduleId); + if (moduleDetails != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Installed module found in distribution: " + moduleId); + } + } + else + { + // Get the specifics of the missing module + RegistryKey moduleKeyCurrentVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); + VersionNumber versionCurrent = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); + // The module is missing, so warn + loggerService.warn(I18NUtil.getMessage(MSG_MISSING, moduleId, versionCurrent)); + } + } + } + + /** + * Copies, where necessary, the module registry details from the alias details + * and removes the alias details. + */ + private void renameModule(ModuleDetails module) + { + String moduleId = module.getId(); + List moduleAliases = module.getAliases(); + + // Get the IDs of all modules from the registry + RegistryKey moduleKeyAllIds = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, null); + Collection registeredModuleIds = registryService.getChildElements(moduleKeyAllIds); + + // Firstly, is the module installed? + if (registeredModuleIds.contains(moduleId)) + { + // It is there, so we do nothing + return; + } + // Check if any of the registered modules are on the alias list + for (String moduleAlias : moduleAliases) + { + // Is this alias registered? + if (!registeredModuleIds.contains(moduleAlias)) + { + // No alias registered + continue; + } + // We found an alias and have to rename it to the new module ID + RegistryKey moduleKeyNew = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, null); + RegistryKey moduleKeyOld = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleAlias, null); + // Copy it all + registryService.copy(moduleKeyOld, moduleKeyNew); + // Remove the source + registryService.delete(moduleKeyOld); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Moved old module alias to new module ID: \n" + + " Alias: " + moduleAlias + "\n" + + " Module: " + moduleId); + } + break; + } + } + + /** + * Does the actual work without fussing about transactions and authentication. + * Module dependencies will be started first, but a module will only be started + * once. + * + * @param module the module to start + * @param startedModules the IDs of modules that have already started + * @param executedComponents keep track of the executed components + */ + private void startModule(ModuleDetails module, Set startedModules, Set executedComponents) + { + String moduleId = module.getId(); + VersionNumber moduleNewVersion = module.getVersion(); + + // Double check whether we have done this module already + if (startedModules.contains(moduleId)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Module '" + module + "' already started"); + } + return; + } + + // Start dependencies + List moduleDependencies = module.getDependencies(); + for (ModuleDependency moduleDependency : moduleDependencies) + { + if (logger.isDebugEnabled()) + { + logger.debug("Module '" + module + "' depends on: " + moduleDependency); + } + // Get the dependency + String moduleDependencyId = moduleDependency.getDependencyId(); + ModuleDetails moduleDependencyDetails = moduleService.getModule(moduleDependencyId); + // Check that it is there + if (moduleDependencyDetails == null) + { + // The dependency is not there + // List required dependencies + StringBuilder sb = new StringBuilder(128); + for (ModuleDependency dependency : moduleDependencies) + { + sb.append("\n").append(dependency); + } + String msg = I18NUtil.getMessage( + MSG_DEPENDENCIES, + moduleId, moduleNewVersion, sb.toString()); + logger.info(msg); + // Now fail + throw AlfrescoRuntimeException.create( + ERR_MISSING_DEPENDENCY, + moduleId, moduleNewVersion, moduleDependency); + } + // The dependency is installed, so start it + startModule(moduleDependencyDetails, startedModules, executedComponents); + } + + // Check if the module needs a rename first + renameModule(module); + + // First check that the module version is fundamentally compatible with the repository + VersionNumber repoVersionNumber = descriptorService.getServerDescriptor().getVersionNumber(); + VersionNumber minRepoVersionNumber = module.getRepoVersionMin(); + VersionNumber maxRepoVersionNumber = module.getRepoVersionMax(); + if ((minRepoVersionNumber != null && repoVersionNumber.compareTo(minRepoVersionNumber) < 0) || + (maxRepoVersionNumber != null && repoVersionNumber.compareTo(maxRepoVersionNumber) > 0)) + { + // The current repo version is not supported + throw AlfrescoRuntimeException.create( + ERR_UNSUPPORTED_REPO_VERSION, + moduleId, moduleNewVersion, repoVersionNumber, minRepoVersionNumber, maxRepoVersionNumber); + } + + // Get the module details from the registry + RegistryKey moduleKeyInstalledVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_INSTALLED_VERSION); + RegistryKey moduleKeyCurrentVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); + VersionNumber moduleInstallVersion = (VersionNumber) registryService.getProperty(moduleKeyInstalledVersion); + VersionNumber moduleCurrentVersion = (VersionNumber) registryService.getProperty(moduleKeyCurrentVersion); + String msg = null; + if (moduleCurrentVersion == null) // No previous record of it + { + msg = I18NUtil.getMessage(MSG_INSTALLING, moduleId, moduleNewVersion); + // Record the install version + registryService.addProperty(moduleKeyInstalledVersion, moduleNewVersion); + moduleInstallVersion = moduleNewVersion; + moduleCurrentVersion = moduleNewVersion; + } + else // It is an upgrade or is the same + { + // Check that we have an installed version + if (moduleInstallVersion == null) + { + // A current version, but no installed version + logger.warn(I18NUtil.getMessage(WARN_NO_INSTALL_VERSION, moduleId, moduleCurrentVersion)); + // Record the install version + registryService.addProperty(moduleKeyInstalledVersion, moduleCurrentVersion); + moduleInstallVersion = moduleCurrentVersion; + } + + if (moduleCurrentVersion.compareTo(moduleNewVersion) == 0) // The current version is the same + { + msg = I18NUtil.getMessage(MSG_STARTING, moduleId, moduleNewVersion); + } + else if (moduleCurrentVersion.compareTo(moduleNewVersion) > 0) // Downgrading not supported + { + throw AlfrescoRuntimeException.create(ERR_NO_DOWNGRADE, moduleId, moduleCurrentVersion, moduleNewVersion); + } + else // This is an upgrade + { + msg = I18NUtil.getMessage(MSG_UPGRADING, moduleId, moduleNewVersion, moduleCurrentVersion); + } + } + loggerService.info(msg); + // Record the current version + registryService.addProperty(moduleKeyCurrentVersion, moduleNewVersion); + + Map componentsByName = getComponents(moduleId); + for (ModuleComponent component : componentsByName.values()) + { + executeComponent(moduleId, moduleInstallVersion, component, executedComponents); + } + + // Keep track of the ID as it started successfully + startedModules.add(moduleId); + + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Started module '" + module + "' including " + executedComponents.size() + "components."); + } + } + + /** + * Execute the component, respecting dependencies. + */ + private void executeComponent( + String moduleId, + VersionNumber moduleInstallVersion, + ModuleComponent component, + Set executedComponents) + { + // Ignore if it has been executed in this run already + if (executedComponents.contains(component)) + { + // Already done + if (logger.isDebugEnabled()) + { + logger.debug("Skipping component already executed in this run: \n" + + " Component: " + component); + } + return; + } + // Keep track of the fact that we considered it for execution + executedComponents.add(component); + + // Check the version applicability + VersionNumber minVersion = component.getAppliesFromVersionNumber(); + VersionNumber maxVersion = component.getAppliesToVersionNumber(); + if (moduleInstallVersion.compareTo(minVersion) < 0 || moduleInstallVersion.compareTo(maxVersion) > 0) + { + // It is out of the allowable range for execution so we just ignore it + if (logger.isDebugEnabled()) + { + logger.debug("Skipping component that doesn't apply to the module installation version: \n" + + " Component: " + component + "\n" + + " Module: " + moduleId + "\n" + + " Install Version: " + moduleInstallVersion + "\n" + + " Applies From : " + minVersion + "\n" + + " Applies To : " + maxVersion); + } + return; + } + + // Construct the registry key to store the execution date + String name = component.getName(); + RegistryKey executionDateKey = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PATH_COMPONENTS, name, REGISTRY_PROPERTY_EXECUTION_DATE); + + // Check if the component has been executed + Date executionDate = (Date) registryService.getProperty(executionDateKey); + if (executionDate != null && component.isExecuteOnceOnly()) + { + // It has been executed and is scheduled for a single execution - leave it + if (logger.isDebugEnabled()) + { + logger.debug("Skipping already-executed module component: \n" + + " Component: " + component + "\n" + + " Execution Time: " + executionDate); + } + return; + } + // It may have been executed, but not in this run and it is allowed to be repeated + // Check for dependencies + List dependencies = component.getDependsOn(); + for (ModuleComponent dependency : dependencies) + { + executeComponent(moduleId, moduleInstallVersion, dependency, executedComponents); + } + // Execute the component itself + component.execute(); + // Keep track of it in the registry + registryService.addProperty(executionDateKey, new Date()); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Executed module component: \n" + + " Component: " + component); + } + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java index e3e6334e8b..86bf7e3916 100644 --- a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java +++ b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java @@ -1,214 +1,204 @@ -/* - * Copyright (C) 2005-2008 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.repo.module; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.admin.registry.RegistryService; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.module.ModuleDetails; -import org.alfresco.service.cmr.module.ModuleService; -import org.alfresco.service.descriptor.DescriptorService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; - -/** - * This component controls the execution of - * {@link org.alfresco.repo.module.runtime.ModuleComponent module startup components}. - *

- * All required startup executions are performed in a single transaction, so this - * component guarantees that the module initialization is consistent. Module components are - * executed in dependency order only. The version numbering is not to be used - * for ordering purposes. - *

- * Afterwards, execution details are persisted in the - * {@link org.alfresco.repo.admin.registry.RegistryService service registry} to be used when the - * server starts up again. - * - * @author Roy Wetherall - * @author Derek Hulley - * @since 2.0 - */ -public class ModuleServiceImpl implements ModuleService -{ - /** Error messages **/ - private static final String ERR_UNABLE_TO_OPEN_MODULE_PROPETIES = "module.err.unable_to_open_module_properties"; - - /** The classpath search path for module properties */ - private static final String MODULE_CONFIG_SEARCH_ALL = "classpath*:alfresco/module/*/module.properties"; - - private static Log logger = LogFactory.getLog(ModuleServiceImpl.class); - - private ServiceRegistry serviceRegistry; - private ModuleComponentHelper moduleComponentHelper; - /** A cache of module details by module ID */ - private Map moduleDetailsById; - - /** Default constructor */ - public ModuleServiceImpl() - { - moduleComponentHelper = new ModuleComponentHelper(); - moduleComponentHelper.setModuleService(this); - } - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.serviceRegistry = serviceRegistry; - this.moduleComponentHelper.setServiceRegistry(this.serviceRegistry); - } - - public void setDescriptorService(DescriptorService descriptorService) - { - this.moduleComponentHelper.setDescriptorService(descriptorService); - } - - /** - * No longer used - * @deprecated - */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - logger.warn("Bean property 'authenticationComponent' is no longer required for 'ModuleServiceImpl'."); - } - - /** - * @param registryService the service used to persist component execution details. - */ - public void setRegistryService(RegistryService registryService) - { - this.moduleComponentHelper.setRegistryService(registryService); - } - - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.moduleComponentHelper.setTenantAdminService(tenantAdminService); - } - - /** - * @see ModuleComponentHelper#registerComponent(ModuleComponent) - */ - public void registerComponent(ModuleComponent component) - { - this.moduleComponentHelper.registerComponent(component); - } - - /** - * {@inheritDoc} - * - * @see ModuleComponentHelper#startModules() - */ - public void startModules() - { - moduleComponentHelper.startModules(); - } - - /** - * {@inheritDoc} - */ - public ModuleDetails getModule(String moduleId) - { - cacheModuleDetails(); - // Get the details of the specific module - ModuleDetails details = moduleDetailsById.get(moduleId); - // Done - return details; - } - - /** - * {@inheritDoc} - */ - public List getAllModules() - { - cacheModuleDetails(); - Collection moduleDetails = moduleDetailsById.values(); - // Make a copy to avoid modification of cached data by clients (and to satisfy API) - List result = new ArrayList(moduleDetails); - // Done - return result; - } - - /** - * Ensure that the {@link #moduleDetailsById module details} are populated. - *

- * TODO: We will have to avoid caching or add context listening if we support reloading - * of beans one day. - */ - private synchronized void cacheModuleDetails() - { - if (moduleDetailsById != null) - { - // There is nothing to do - return; - } - try - { - moduleDetailsById = new HashMap(13); - - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - Resource[] resources = resolver.getResources(MODULE_CONFIG_SEARCH_ALL); - - // Read each resource - for (Resource resource : resources) - { - try - { - InputStream is = new BufferedInputStream(resource.getInputStream()); - Properties properties = new Properties(); - properties.load(is); - ModuleDetails details = new ModuleDetailsImpl(properties); - moduleDetailsById.put(details.getId(), details); - } - catch (Throwable e) - { - throw AlfrescoRuntimeException.create(e, ERR_UNABLE_TO_OPEN_MODULE_PROPETIES, resource); - } - } - } - catch (IOException e) - { - throw new AlfrescoRuntimeException("Failed to retrieve module information", e); - } - // Done - if (logger.isDebugEnabled()) - { - logger.debug( - "Found " + moduleDetailsById.size() + " modules: \n" + - " Modules: " + moduleDetailsById); - } - } -} +/* + * Copyright (C) 2005-2008 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.repo.module; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.registry.RegistryService; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.descriptor.DescriptorService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +/** + * This component controls the execution of + * {@link org.alfresco.repo.module.runtime.ModuleComponent module startup components}. + *

+ * All required startup executions are performed in a single transaction, so this + * component guarantees that the module initialization is consistent. Module components are + * executed in dependency order only. The version numbering is not to be used + * for ordering purposes. + *

+ * Afterwards, execution details are persisted in the + * {@link org.alfresco.repo.admin.registry.RegistryService service registry} to be used when the + * server starts up again. + * + * @author Roy Wetherall + * @author Derek Hulley + * @since 2.0 + */ +public class ModuleServiceImpl implements ModuleService +{ + /** Error messages **/ + private static final String ERR_UNABLE_TO_OPEN_MODULE_PROPETIES = "module.err.unable_to_open_module_properties"; + + /** The classpath search path for module properties */ + private static final String MODULE_CONFIG_SEARCH_ALL = "classpath*:alfresco/module/*/module.properties"; + + private static Log logger = LogFactory.getLog(ModuleServiceImpl.class); + + private ServiceRegistry serviceRegistry; + private ModuleComponentHelper moduleComponentHelper; + /** A cache of module details by module ID */ + private Map moduleDetailsById; + + /** Default constructor */ + public ModuleServiceImpl() + { + moduleComponentHelper = new ModuleComponentHelper(); + moduleComponentHelper.setModuleService(this); + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + this.moduleComponentHelper.setServiceRegistry(this.serviceRegistry); + } + + public void setDescriptorService(DescriptorService descriptorService) + { + this.moduleComponentHelper.setDescriptorService(descriptorService); + } + + /** + * @param registryService the service used to persist component execution details. + */ + public void setRegistryService(RegistryService registryService) + { + this.moduleComponentHelper.setRegistryService(registryService); + } + + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.moduleComponentHelper.setTenantAdminService(tenantAdminService); + } + + /** + * @see ModuleComponentHelper#registerComponent(ModuleComponent) + */ + public void registerComponent(ModuleComponent component) + { + this.moduleComponentHelper.registerComponent(component); + } + + /** + * {@inheritDoc} + * + * @see ModuleComponentHelper#startModules() + */ + public void startModules() + { + moduleComponentHelper.startModules(); + } + + /** + * {@inheritDoc} + */ + public ModuleDetails getModule(String moduleId) + { + cacheModuleDetails(); + // Get the details of the specific module + ModuleDetails details = moduleDetailsById.get(moduleId); + // Done + return details; + } + + /** + * {@inheritDoc} + */ + public List getAllModules() + { + cacheModuleDetails(); + Collection moduleDetails = moduleDetailsById.values(); + // Make a copy to avoid modification of cached data by clients (and to satisfy API) + List result = new ArrayList(moduleDetails); + // Done + return result; + } + + /** + * Ensure that the {@link #moduleDetailsById module details} are populated. + *

+ * TODO: We will have to avoid caching or add context listening if we support reloading + * of beans one day. + */ + private synchronized void cacheModuleDetails() + { + if (moduleDetailsById != null) + { + // There is nothing to do + return; + } + try + { + moduleDetailsById = new HashMap(13); + + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources(MODULE_CONFIG_SEARCH_ALL); + + // Read each resource + for (Resource resource : resources) + { + try + { + InputStream is = new BufferedInputStream(resource.getInputStream()); + Properties properties = new Properties(); + properties.load(is); + ModuleDetails details = new ModuleDetailsImpl(properties); + moduleDetailsById.put(details.getId(), details); + } + catch (Throwable e) + { + throw AlfrescoRuntimeException.create(e, ERR_UNABLE_TO_OPEN_MODULE_PROPETIES, resource); + } + } + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to retrieve module information", e); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Found " + moduleDetailsById.size() + " modules: \n" + + " Modules: " + moduleDetailsById); + } + } +} diff --git a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java index 6cddff0c49..0f96cfb925 100644 --- a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java +++ b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java @@ -1,369 +1,369 @@ -/* - * 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.repo.preference; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.service.cmr.preference.PreferenceService; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Preference Service Implementation - * - * @author Roy Wetherall - */ -public class PreferenceServiceImpl implements PreferenceService -{ - /** Node service */ - private NodeService nodeService; - - /** Content service */ - private ContentService contentService; - - /** Person service */ - private PersonService personService; - - /** Permission Service */ - private PermissionService permissionService; - - /** Authentication Service */ - private AuthenticationComponent authenticationComponent; - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the content service - * - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * Set the person service - * - * @param personService the person service - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * Set the permission service - * - * @param permissionService the permission service - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Set the authentication component - * - * @param authenticationComponent the authentication component - */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - /** - * @see org.alfresco.service.cmr.preference.PreferenceService#getPreferences(java.lang.String) - */ - public Map getPreferences(String userName) - { - return getPreferences(userName, null); - } - - /** - * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, java.lang.String) - */ - public Map getPreferences(String userName, String preferenceFilter) - { - Map preferences = new HashMap(20); - - // Get the user node reference - NodeRef personNodeRef = this.personService.getPerson(userName); - if (personNodeRef == null) - { - throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because he/she does not exist."); - } - - try - { - // Check for preferences aspect - if (this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) - { - // Get the preferences for this user - JSONObject jsonPrefs = new JSONObject(); - ContentReader reader = this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); - if (reader != null) - { - jsonPrefs = new JSONObject(reader.getContentString()); - } - - // Build hash from preferences stored in the repository - Iterator keys = jsonPrefs.keys(); - while (keys.hasNext()) - { - String key = (String)keys.next(); - - if (preferenceFilter == null || - preferenceFilter.length() == 0 || - matchPreferenceNames(key, preferenceFilter) == true) - { - preferences.put(key, (Serializable)jsonPrefs.get(key)); - } - } - } - } - catch (JSONException exception) - { - throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); - } - - return preferences; - } - - /** - * Matches the preference name to the partial preference name provided - * - * @param name preference name - * @param matchTo match to the partial preference name provided - * @return boolean true if matches, false otherwise - */ - private boolean matchPreferenceNames(String name, String matchTo) - { - boolean result = true; - - // Split strings - name = name.replace(".", "-"); - String[] nameArr = name.split("-"); - matchTo = matchTo.replace(".", "-"); - String[] matchToArr = matchTo.split("-"); - - int index = 0; - for (String matchToElement : matchToArr) - { - if (matchToElement.equals(nameArr[index]) == false) - { - result = false; - break; - } - index ++; - } - - return result; - } - - /** - * @see org.alfresco.repo.person.PersonService#setPreferences(java.lang.String, java.util.HashMap) - */ - public void setPreferences(final String userName, final Map preferences) - { - // Get the user node reference - final NodeRef personNodeRef = this.personService.getPerson(userName); - if (personNodeRef == null) - { - throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because he/she does not exist."); - } - - // Can only set preferences if the currently logged in user matches the user name being updated or - // the user already has write permissions on the person node - String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if (authenticationComponent.isSystemUserName(currentUserName) == true || - permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || - userName.equals(currentUserName) == true) - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - // Apply the preferences aspect if required - if (PreferenceServiceImpl.this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == false) - { - PreferenceServiceImpl.this.nodeService.addAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES, null); - } - - try - { - // Get the current preferences - JSONObject jsonPrefs = new JSONObject(); - ContentReader reader = PreferenceServiceImpl.this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); - if (reader != null) - { - jsonPrefs = new JSONObject(reader.getContentString()); - } - - // Update with the new preference values - for (Map.Entry entry : preferences.entrySet()) - { - jsonPrefs.put(entry.getKey(), entry.getValue()); - } - - // Save the updated preferences - ContentWriter contentWriter = PreferenceServiceImpl.this.contentService.getWriter(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES, true); - contentWriter.setEncoding("UTF-8"); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - contentWriter.putContent(jsonPrefs.toString()); - } - catch (JSONException exception) - { - throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because there was an error pasing the JSON data.", exception); - } - - return null; - } - - }, AuthenticationUtil.SYSTEM_USER_NAME); - } - else - { - // The current user does not have sufficient permissions to update the preferences for this user - throw new AlfrescoRuntimeException("The current user " + currentUserName + " does not have sufficient permissions to update the preferences of the user " + userName); - } - } - - /** - * @see org.alfresco.service.cmr.preference.PreferenceService#clearPreferences(java.lang.String) - */ - public void clearPreferences(String userName) - { - clearPreferences(userName, null); - } - - /** - * @see org.alfresco.repo.person.PersonService#clearPreferences(java.lang.String, java.lang.String) - */ - public void clearPreferences(final String userName, final String preferenceFilter) - { - // Get the user node reference - final NodeRef personNodeRef = this.personService.getPerson(userName); - if (personNodeRef == null) - { - throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because he/she does not exist."); - } - - // Can only set preferences if the currently logged in user matches the user name being updated or - // the user already has write permissions on the person node - String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if (authenticationComponent.isSystemUserName(currentUserName) == true || - permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || - userName.equals(currentUserName) == true) - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - if (PreferenceServiceImpl.this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) - { - try - { - JSONObject jsonPrefs = new JSONObject(); - if (preferenceFilter != null && preferenceFilter.length() != 0) - { - // Get the current preferences - ContentReader reader = PreferenceServiceImpl.this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); - if (reader != null) - { - jsonPrefs = new JSONObject(reader.getContentString()); - } - - // Remove the prefs that match the filter - List removeKeys = new ArrayList(10); - Iterator keys = jsonPrefs.keys(); - while (keys.hasNext()) - { - String key = (String)keys.next(); - - if (preferenceFilter == null || - preferenceFilter.length() == 0 || - matchPreferenceNames(key, preferenceFilter) == true) - { - removeKeys.add(key); - } - } - for (String removeKey : removeKeys) - { - jsonPrefs.remove(removeKey); - } - } - - // Put the updated JSON back into the repo - ContentWriter contentWriter = PreferenceServiceImpl.this.contentService.getWriter(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES, true); - contentWriter.setEncoding("UTF-8"); - contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - contentWriter.putContent(jsonPrefs.toString()); - } - catch (JSONException exception) - { - throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because there was an error pasing the JSON data.", exception); - } - } - - return null; - } - }, AuthenticationUtil.getAdminUserName()); - } - else - { - // The current user does not have sufficient permissions to update the preferences for this user - throw new AlfrescoRuntimeException("The current user " + currentUserName + " does not have sufficient permissions to update the preferences of the user " + userName); - } - } - -} +/* + * 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.repo.preference; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Preference Service Implementation + * + * @author Roy Wetherall + */ +public class PreferenceServiceImpl implements PreferenceService +{ + /** Node service */ + private NodeService nodeService; + + /** Content service */ + private ContentService contentService; + + /** Person service */ + private PersonService personService; + + /** Permission Service */ + private PermissionService permissionService; + + /** Authentication Service */ + private AuthenticationContext authenticationContext; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Set the person service + * + * @param personService the person service + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Set the permission service + * + * @param permissionService the permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Set the authentication component + * + * @param authenticationContext the authentication component + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * @see org.alfresco.service.cmr.preference.PreferenceService#getPreferences(java.lang.String) + */ + public Map getPreferences(String userName) + { + return getPreferences(userName, null); + } + + /** + * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, java.lang.String) + */ + public Map getPreferences(String userName, String preferenceFilter) + { + Map preferences = new HashMap(20); + + // Get the user node reference + NodeRef personNodeRef = this.personService.getPerson(userName); + if (personNodeRef == null) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because he/she does not exist."); + } + + try + { + // Check for preferences aspect + if (this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) + { + // Get the preferences for this user + JSONObject jsonPrefs = new JSONObject(); + ContentReader reader = this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); + if (reader != null) + { + jsonPrefs = new JSONObject(reader.getContentString()); + } + + // Build hash from preferences stored in the repository + Iterator keys = jsonPrefs.keys(); + while (keys.hasNext()) + { + String key = (String)keys.next(); + + if (preferenceFilter == null || + preferenceFilter.length() == 0 || + matchPreferenceNames(key, preferenceFilter) == true) + { + preferences.put(key, (Serializable)jsonPrefs.get(key)); + } + } + } + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + + return preferences; + } + + /** + * Matches the preference name to the partial preference name provided + * + * @param name preference name + * @param matchTo match to the partial preference name provided + * @return boolean true if matches, false otherwise + */ + private boolean matchPreferenceNames(String name, String matchTo) + { + boolean result = true; + + // Split strings + name = name.replace(".", "-"); + String[] nameArr = name.split("-"); + matchTo = matchTo.replace(".", "-"); + String[] matchToArr = matchTo.split("-"); + + int index = 0; + for (String matchToElement : matchToArr) + { + if (matchToElement.equals(nameArr[index]) == false) + { + result = false; + break; + } + index ++; + } + + return result; + } + + /** + * @see org.alfresco.repo.person.PersonService#setPreferences(java.lang.String, java.util.HashMap) + */ + public void setPreferences(final String userName, final Map preferences) + { + // Get the user node reference + final NodeRef personNodeRef = this.personService.getPerson(userName); + if (personNodeRef == null) + { + throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because he/she does not exist."); + } + + // Can only set preferences if the currently logged in user matches the user name being updated or + // the user already has write permissions on the person node + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + if (authenticationContext.isSystemUserName(currentUserName) == true || + permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || + userName.equals(currentUserName) == true) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + // Apply the preferences aspect if required + if (PreferenceServiceImpl.this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == false) + { + PreferenceServiceImpl.this.nodeService.addAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES, null); + } + + try + { + // Get the current preferences + JSONObject jsonPrefs = new JSONObject(); + ContentReader reader = PreferenceServiceImpl.this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); + if (reader != null) + { + jsonPrefs = new JSONObject(reader.getContentString()); + } + + // Update with the new preference values + for (Map.Entry entry : preferences.entrySet()) + { + jsonPrefs.put(entry.getKey(), entry.getValue()); + } + + // Save the updated preferences + ContentWriter contentWriter = PreferenceServiceImpl.this.contentService.getWriter(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES, true); + contentWriter.setEncoding("UTF-8"); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.putContent(jsonPrefs.toString()); + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + + return null; + } + + }, AuthenticationUtil.SYSTEM_USER_NAME); + } + else + { + // The current user does not have sufficient permissions to update the preferences for this user + throw new AlfrescoRuntimeException("The current user " + currentUserName + " does not have sufficient permissions to update the preferences of the user " + userName); + } + } + + /** + * @see org.alfresco.service.cmr.preference.PreferenceService#clearPreferences(java.lang.String) + */ + public void clearPreferences(String userName) + { + clearPreferences(userName, null); + } + + /** + * @see org.alfresco.repo.person.PersonService#clearPreferences(java.lang.String, java.lang.String) + */ + public void clearPreferences(final String userName, final String preferenceFilter) + { + // Get the user node reference + final NodeRef personNodeRef = this.personService.getPerson(userName); + if (personNodeRef == null) + { + throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because he/she does not exist."); + } + + // Can only set preferences if the currently logged in user matches the user name being updated or + // the user already has write permissions on the person node + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + if (authenticationContext.isSystemUserName(currentUserName) == true || + permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || + userName.equals(currentUserName) == true) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + if (PreferenceServiceImpl.this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) + { + try + { + JSONObject jsonPrefs = new JSONObject(); + if (preferenceFilter != null && preferenceFilter.length() != 0) + { + // Get the current preferences + ContentReader reader = PreferenceServiceImpl.this.contentService.getReader(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES); + if (reader != null) + { + jsonPrefs = new JSONObject(reader.getContentString()); + } + + // Remove the prefs that match the filter + List removeKeys = new ArrayList(10); + Iterator keys = jsonPrefs.keys(); + while (keys.hasNext()) + { + String key = (String)keys.next(); + + if (preferenceFilter == null || + preferenceFilter.length() == 0 || + matchPreferenceNames(key, preferenceFilter) == true) + { + removeKeys.add(key); + } + } + for (String removeKey : removeKeys) + { + jsonPrefs.remove(removeKey); + } + } + + // Put the updated JSON back into the repo + ContentWriter contentWriter = PreferenceServiceImpl.this.contentService.getWriter(personNodeRef, ContentModel.PROP_PREFERENCE_VALUES, true); + contentWriter.setEncoding("UTF-8"); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter.putContent(jsonPrefs.toString()); + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not update preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + } + + return null; + } + }, AuthenticationUtil.getAdminUserName()); + } + else + { + // The current user does not have sufficient permissions to update the preferences for this user + throw new AlfrescoRuntimeException("The current user " + currentUserName + " does not have sufficient permissions to update the preferences of the user " + userName); + } + } + +} diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java index 6d89995646..1cf8442d1b 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java @@ -1,125 +1,123 @@ -/* - * 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.repo.security.authentication; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; - -import net.sf.acegisecurity.Authentication; -import net.sf.acegisecurity.GrantedAuthority; -import net.sf.acegisecurity.GrantedAuthorityImpl; -import net.sf.acegisecurity.UserDetails; -import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import net.sf.acegisecurity.providers.dao.User; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.transaction.TransactionService; - -/** - * This class abstract the support required to set up and query the Acegi context for security enforcement. There are - * some simple default method implementations to support simple authentication. - * - * @author Andy Hind - */ -public abstract class AbstractAuthenticationComponent implements AuthenticationComponent -{ - /** - * The abstract class keeps track of support for guest login - */ - private Boolean allowGuestLogin = null; - - private TenantService tenantService; - - private PersonService personService; - - private NodeService nodeService; - - private TransactionService transactionService; - - private Set defaultAdministratorUserNames = Collections.emptySet(); - - private boolean autoCreatePeopleOnLogin = true; - - public AbstractAuthenticationComponent() - { - super(); - } - - /** - * Set if guest login is supported. - * - * @param allowGuestLogin - */ - public void setAllowGuestLogin(Boolean allowGuestLogin) - { - this.allowGuestLogin = allowGuestLogin; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public TransactionService getTransactionService() - { - return transactionService; - } - - public Boolean getAllowGuestLogin() - { - return allowGuestLogin; - } - +/* + * 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.repo.security.authentication; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.dao.User; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; + +/** + * This class abstract the support required to set up and query the Acegi context for security enforcement. There are + * some simple default method implementations to support simple authentication. + * + * @author Andy Hind + */ +public abstract class AbstractAuthenticationComponent implements AuthenticationComponent +{ + /** + * The abstract class keeps track of support for guest login + */ + private Boolean allowGuestLogin = null; + + private Set defaultAdministratorUserNames = Collections.emptySet(); + + private boolean autoCreatePeopleOnLogin = true; + + private AuthenticationContext authenticationContext; + + private PersonService personService; + + private NodeService nodeService; + + private TransactionService transactionService; + + public AbstractAuthenticationComponent() + { + super(); + } + + /** + * Set if guest login is supported. + * + * @param allowGuestLogin + */ + public void setAllowGuestLogin(Boolean allowGuestLogin) + { + this.allowGuestLogin = allowGuestLogin; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + public Boolean getAllowGuestLogin() + { + return allowGuestLogin; + } + public NodeService getNodeService() { return nodeService; @@ -130,56 +128,56 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC return personService; } - public boolean isAutoCreatePeopleOnLogin() - { - return autoCreatePeopleOnLogin; - } - - public void setAutoCreatePeopleOnLogin(boolean autoCreatePeopleOnLogin) - { - this.autoCreatePeopleOnLogin = autoCreatePeopleOnLogin; - } - - public void authenticate(String userName, char[] password) throws AuthenticationException - { - // Support guest login from the login screen - if (isGuestUserName(userName)) - { - setGuestUserAsCurrentUser(tenantService.getUserDomain(userName)); - } - else - { - authenticateImpl(userName, password); - } - } - - /** - * Default unsupported authentication implementation - as of 2.1 this is the best way to implement your own - * authentication component as it will support guest login - prior to this direct over ride for authenticate(String , - * char[]) was used. This will still work. - * - * @param userName - * @param password - */ - protected void authenticateImpl(String userName, char[] password) - { - throw new UnsupportedOperationException(); - } - - public Authentication setCurrentUser(String userName, UserNameValidationMode validationMode) - { - switch (validationMode) - { - case NONE: + public boolean isAutoCreatePeopleOnLogin() + { + return autoCreatePeopleOnLogin; + } + + public void setAutoCreatePeopleOnLogin(boolean autoCreatePeopleOnLogin) + { + this.autoCreatePeopleOnLogin = autoCreatePeopleOnLogin; + } + + public void authenticate(String userName, char[] password) throws AuthenticationException + { + // Support guest login from the login screen + if (isGuestUserName(userName)) + { + setGuestUserAsCurrentUser(getUserDomain(userName)); + } + else + { + authenticateImpl(userName, password); + } + } + + /** + * Default unsupported authentication implementation - as of 2.1 this is the best way to implement your own + * authentication component as it will support guest login - prior to this direct over ride for authenticate(String , + * char[]) was used. This will still work. + * + * @param userName + * @param password + */ + protected void authenticateImpl(String userName, char[] password) + { + throw new UnsupportedOperationException(); + } + + public Authentication setCurrentUser(String userName, UserNameValidationMode validationMode) + { + switch (validationMode) + { + case NONE: return setCurrentUserImpl(userName); - case CHECK_AND_FIX: - default: - return setCurrentUser(userName); - } - } - - public Authentication setCurrentUser(final String userName) throws AuthenticationException - { + case CHECK_AND_FIX: + default: + return setCurrentUser(userName); + } + } + + public Authentication setCurrentUser(final String userName) throws AuthenticationException + { if (isSystemUserName(userName)) { return setCurrentUserImpl(userName); @@ -223,39 +221,29 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC throw new AuthenticationException("Null user name"); } + if (isSystemUserName(userName)) + { + return setSystemUserAsCurrentUser(getUserDomain(userName)); + } + try { UserDetails ud = null; - if (isSystemUserName(userName)) - { - GrantedAuthority[] gas = new GrantedAuthority[1]; - gas[0] = new GrantedAuthorityImpl("ROLE_SYSTEM"); - ud = new User(userName, "", true, true, true, true, gas); - } - else if (isGuestUserName(userName)) + if (isGuestUserName(userName)) { GrantedAuthority[] gas = new GrantedAuthority[0]; - ud = new User(getGuestUserName(tenantService.getUserDomain(userName)), "", true, true, true, true, gas); + ud = new User(getGuestUserName(getUserDomain(userName)), "", true, true, true, true, gas); } else { ud = getUserDetails(userName); } - - UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(ud, "", ud.getAuthorities()); - auth.setDetails(ud); - auth.setAuthenticated(true); - return setCurrentAuthentication(auth); + return setUserDetails(ud); } catch (net.sf.acegisecurity.AuthenticationException ae) { throw new AuthenticationException(ae.getMessage(), ae); } - finally - { - // Support for logging tenantdomain / username (via log4j NDC) - AuthenticationUtil.logNDC(userName); - } } /** @@ -270,175 +258,177 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); UserDetails ud = new User(userName, "", true, true, true, true, gas); return ud; - } - - /** - * {@inheritDoc} - */ - public Authentication setCurrentAuthentication(Authentication authentication) - { - return AuthenticationUtil.setFullAuthentication(authentication); - } - - /** - * Get the current authentication context - * - * @return Authentication - * @throws AuthenticationException - */ - public Authentication getCurrentAuthentication() throws AuthenticationException - { - return AuthenticationUtil.getFullAuthentication(); - } - - /** - * Get the current user name. - * - * @return String - * @throws AuthenticationException - */ - public String getCurrentUserName() throws AuthenticationException - { - return AuthenticationUtil.getFullyAuthenticatedUser(); - } - - /** - * Set the system user as the current user note: for MT, will set to default domain only - * - * @return Authentication - */ - public Authentication setSystemUserAsCurrentUser() - { - return setCurrentUser(AuthenticationUtil.SYSTEM_USER_NAME); - } - - /** - * Get the name of the system user note: for MT, will get system for default domain only - * - * @return String - */ - public String getSystemUserName() - { - return AuthenticationUtil.SYSTEM_USER_NAME; - } - - /** - * Is this the system user ? - * - * @return boolean - */ - public boolean isSystemUserName(String userName) - { - return (getSystemUserName().equals(tenantService.getBaseNameUser(userName))); - } - - /** - * Get the name of the Guest User note: for MT, will get guest for default domain only - * - * @return String - */ - public String getGuestUserName() - { - return PermissionService.GUEST_AUTHORITY.toLowerCase(); - } - - private String getGuestUserName(String tenantDomain) - { - return tenantService.getDomainUser(getGuestUserName(), tenantDomain); - } - - /** - * Set the guest user as the current user. note: for MT, will set to default domain only - */ - public Authentication setGuestUserAsCurrentUser() throws AuthenticationException - { - return setGuestUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); - } - - /** - * Set the guest user as the current user. - */ - private Authentication setGuestUserAsCurrentUser(String tenantDomain) throws AuthenticationException - { - if (allowGuestLogin == null) - { - if (implementationAllowsGuestLogin()) - { - return setCurrentUser(getGuestUserName(tenantDomain)); - } - else - { - throw new AuthenticationException("Guest authentication is not allowed"); - } - } - else - { - if (allowGuestLogin.booleanValue()) - { - return setCurrentUser(getGuestUserName(tenantDomain)); - } - else - { - throw new AuthenticationException("Guest authentication is not allowed"); - } - - } - } - - private boolean isGuestUserName(String userName) - { - return (PermissionService.GUEST_AUTHORITY.equalsIgnoreCase(tenantService.getBaseNameUser(userName))); - } - - protected abstract boolean implementationAllowsGuestLogin(); - - /** - * @return true if Guest user authentication is allowed, false otherwise - */ - public boolean guestUserAuthenticationAllowed() - { - if (allowGuestLogin == null) - { - return (implementationAllowsGuestLogin()); - } - else - { - return (allowGuestLogin.booleanValue()); - } - } - - /** - * Remove the current security information - */ - public void clearCurrentSecurityContext() - { - AuthenticationUtil.clearCurrentSecurityContext(); - } - - /** - * The default is not to support Authentication token base authentication - */ - public Authentication authenticate(Authentication token) throws AuthenticationException - { - throw new AlfrescoRuntimeException("Authentication via token not supported"); - } - - /** - * The should only be supported if getNTLMMode() is NTLMMode.MD4_PROVIDER. - */ - public String getMD4HashedPassword(String userName) - { - throw new UnsupportedOperationException(); - } - - /** - * Get the NTML mode - none - supports MD4 hash to integrate - or it can asct as an NTLM authentication - */ - public NTLMMode getNTLMMode() - { - return NTLMMode.NONE; - } - + } + + /** + * {@inheritDoc} + */ + public Authentication setCurrentAuthentication(Authentication authentication) + { + return this.authenticationContext.setCurrentAuthentication(authentication); + } + + /** + * Get the current authentication context + * + * @return Authentication + * @throws AuthenticationException + */ + public Authentication getCurrentAuthentication() throws AuthenticationException + { + return authenticationContext.getCurrentAuthentication(); + } + + /** + * Get the current user name. + * + * @return String + * @throws AuthenticationException + */ + public String getCurrentUserName() throws AuthenticationException + { + return authenticationContext.getCurrentUserName(); + } + + /** + * Set the system user as the current user note: for MT, will set to default domain only + * + * @return Authentication + */ + public Authentication setSystemUserAsCurrentUser() + { + return authenticationContext.setSystemUserAsCurrentUser(); + } + + /** + * Get the name of the system user note: for MT, will get system for default domain only + * + * @return String + */ + public String getSystemUserName() + { + return authenticationContext.getSystemUserName(); + } + + /** + * Is this the system user ? + * + * @return boolean + */ + public boolean isSystemUserName(String userName) + { + return authenticationContext.isSystemUserName(userName); + } + + /** + * Get the name of the Guest User note: for MT, will get guest for default domain only + * + * @return String + */ + public String getGuestUserName() + { + return authenticationContext.getGuestUserName(); + } + + public String getGuestUserName(String tenantDomain) + { + return authenticationContext.getGuestUserName(tenantDomain); + } + + /** + * Set the guest user as the current user. note: for MT, will set to default domain only + */ + public Authentication setGuestUserAsCurrentUser() throws AuthenticationException + { + return setGuestUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); + } + + /** + * Set the guest user as the current user. + */ + private Authentication setGuestUserAsCurrentUser(String tenantDomain) throws AuthenticationException + { + if (allowGuestLogin == null) + { + if (implementationAllowsGuestLogin()) + { + return setCurrentUser(getGuestUserName(tenantDomain)); + } + else + { + throw new AuthenticationException("Guest authentication is not allowed"); + } + } + else + { + if (allowGuestLogin.booleanValue()) + { + return setCurrentUser(getGuestUserName(tenantDomain)); + } + else +{ + throw new AuthenticationException("Guest authentication is not allowed"); + } + + } + } + + public boolean isGuestUserName(String userName) + { + return authenticationContext.isGuestUserName(userName); + } + + + protected abstract boolean implementationAllowsGuestLogin(); + + + /** + * @return true if Guest user authentication is allowed, false otherwise + */ + public boolean guestUserAuthenticationAllowed() + { + if (allowGuestLogin == null) + { + return (implementationAllowsGuestLogin()); + } + else + { + return (allowGuestLogin.booleanValue()); + } + } + + /** + * Remove the current security information + */ + public void clearCurrentSecurityContext() + { + authenticationContext.clearCurrentSecurityContext(); + } + + /** + * The default is not to support Authentication token base authentication + */ + public Authentication authenticate(Authentication token) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Authentication via token not supported"); + } + + /** + * The should only be supported if getNTLMMode() is NTLMMode.MD4_PROVIDER. + */ + public String getMD4HashedPassword(String userName) + { + throw new UnsupportedOperationException(); + } + + /** + * Get the NTML mode - none - supports MD4 hash to integrate - or it can asct as an NTLM authentication + */ + public NTLMMode getNTLMMode() + { + return NTLMMode.NONE; + } + class SetCurrentUserCallback implements RetryingTransactionHelper.RetryingTransactionCallback { AuthenticationException ae = null; @@ -490,7 +480,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC return userName; } } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantService.getUserDomain(userName))); + }, getSystemUserName(getUserDomain(userName))); return setCurrentUserImpl(name); } @@ -502,38 +492,58 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC } } - /* - * (non-Javadoc) - * @see org.alfresco.repo.security.authentication.AuthenticationComponent#getDefaultAdministratorUserNames() - */ - public Set getDefaultAdministratorUserNames() - { - return this.defaultAdministratorUserNames; - } - - /** - * Sets the user names who for this particular authentication system should be considered administrators by default. - * - * @param defaultAdministratorUserNames - * a set of user names - */ - public void setDefaultAdministratorUserNames(Set defaultAdministratorUserNames) - { - this.defaultAdministratorUserNames = defaultAdministratorUserNames; - } - - /** - * Convenience method to allow the administrator user names to be specified as a comma separated list - * - * @param defaultAdministratorUserNames - */ - public void setDefaultAdministratorUserNames(String defaultAdministratorUserNames) - { - Set nameSet = new TreeSet(); - if (defaultAdministratorUserNames.length() > 0) - { - nameSet.addAll(Arrays.asList(defaultAdministratorUserNames.split(","))); - } - setDefaultAdministratorUserNames(nameSet); - } -} + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.authentication.AuthenticationComponent#getDefaultAdministratorUserNames() + */ + public Set getDefaultAdministratorUserNames() + { + return this.defaultAdministratorUserNames; + } + + /** + * Sets the user names who for this particular authentication system should be considered administrators by default. + * + * @param defaultAdministratorUserNames + * a set of user names + */ + public void setDefaultAdministratorUserNames(Set defaultAdministratorUserNames) + { + this.defaultAdministratorUserNames = defaultAdministratorUserNames; + } + + /** + * Convenience method to allow the administrator user names to be specified as a comma separated list + * + * @param defaultAdministratorUserNames + */ + public void setDefaultAdministratorUserNames(String defaultAdministratorUserNames) + { + Set nameSet = new TreeSet(); + if (defaultAdministratorUserNames.length() > 0) + { + nameSet.addAll(Arrays.asList(defaultAdministratorUserNames.split(","))); + } + setDefaultAdministratorUserNames(nameSet); + } + + public String getSystemUserName(String tenantDomain) + { + return authenticationContext.getSystemUserName(tenantDomain); + } + + public String getUserDomain(String userName) + { + return authenticationContext.getUserDomain(userName); + } + + public Authentication setSystemUserAsCurrentUser(String tenantDomain) + { + return authenticationContext.setSystemUserAsCurrentUser(tenantDomain); + } + + public Authentication setUserDetails(UserDetails ud) + { + return authenticationContext.setUserDetails(ud); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationComponent.java index f61c5c56ec..d9c0e1664f 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationComponent.java @@ -28,9 +28,8 @@ import java.util.Set; import net.sf.acegisecurity.Authentication; -public interface AuthenticationComponent +public interface AuthenticationComponent extends AuthenticationContext { - public enum UserNameValidationMode { NONE, CHECK_AND_FIX; @@ -65,33 +64,6 @@ public interface AuthenticationComponent public Authentication setCurrentUser(String userName, UserNameValidationMode validationMode); - /** - * Remove the current security information - * - */ - public void clearCurrentSecurityContext(); - - /** - * Explicitly set the current suthentication. If the authentication is null the - * the current authentication is {@link #clearCurrentSecurityContext() cleared}. - * - * @param authentication the current authentication (may be null). - * - * @return Returns the modified authentication instance or null if it was cleared. - */ - public Authentication setCurrentAuthentication(Authentication authentication); - - /** - * - * @throws AuthenticationException - */ - public Authentication getCurrentAuthentication() throws AuthenticationException; - - /** - * Set the system user as the current user. - */ - public Authentication setSystemUserAsCurrentUser(); - /** * Set the guest user as the current user. @@ -104,29 +76,6 @@ public interface AuthenticationComponent */ public boolean guestUserAuthenticationAllowed(); - - /** - * Get the name of the system user - */ - public String getSystemUserName(); - - /** - * True if this is the System user ? - */ - public boolean isSystemUserName(String userName); - - /** - * Get the name of the guest user - */ - public String getGuestUserName(); - - /** - * Get the current user name. - * - * @throws AuthenticationException - */ - public String getCurrentUserName() throws AuthenticationException; - /** * Get the enum that describes NTLM integration */ diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationContext.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationContext.java new file mode 100644 index 0000000000..db302990e8 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationContext.java @@ -0,0 +1,122 @@ +/* + * 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.repo.security.authentication; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.UserDetails; + +/** + * Low-level interface allowing control and retrieval of the authentication information held for the current thread. + * + * @author dward + */ +public interface AuthenticationContext +{ + /** + * Remove the current security information + */ + public void clearCurrentSecurityContext(); + + /** + * Explicitly set the current suthentication. If the authentication is null the the current authentication + * is {@link #clearCurrentSecurityContext() cleared}. + * + * @param authentication + * the current authentication (may be null). + * @return Returns the modified authentication instance or null if it was cleared. + */ + public Authentication setCurrentAuthentication(Authentication authentication); + + /** + * Explicitly set the given validated user details to be authenticated. + * + * @param ud + * the User Details + * @return Authentication + */ + public Authentication setUserDetails(UserDetails ud); + + /** + * @throws AuthenticationException + */ + public Authentication getCurrentAuthentication() throws AuthenticationException; + + /** + * Set the system user as the current user. + */ + public Authentication setSystemUserAsCurrentUser(); + + /** + * Set the system user as the current user. + */ + public Authentication setSystemUserAsCurrentUser(String tenantDomain); + + /** + * Get the name of the system user. Note: for MT, will get system for default domain only + */ + public String getSystemUserName(); + + /** + * Get the name of the system user + */ + public String getSystemUserName(String tenantDomain); + + /** + * True if this is the System user ? + */ + public boolean isSystemUserName(String userName); + + /** + * Get the name of the Guest User. Note: for MT, will get guest for default domain only + */ + public String getGuestUserName(); + + /** + * Get the name of the guest user + */ + public String getGuestUserName(String tenantDomain); + + /** + * True if this is a guest user ? + */ + public boolean isGuestUserName(String userName); + + /** + * Get the current user name. + * + * @throws AuthenticationException + */ + public String getCurrentUserName() throws AuthenticationException; + + /** + * Extracts the tenant domain name from a user name + * + * @param userName + * a user name + * @return a tenant domain name + */ + public String getUserDomain(String userName); + +} diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationContextImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationContextImpl.java new file mode 100644 index 0000000000..d45cd9d192 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationContextImpl.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.authentication; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import net.sf.acegisecurity.providers.dao.User; + +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.security.PermissionService; + +/** + * @author Andy Hind + * @author dward + */ +public class AuthenticationContextImpl implements AuthenticationContext +{ + private TenantService tenantService; + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Explicitly set the given validated user details to be authenticated. + * + * @param ud + * the User Details + * @return Authentication + */ + public Authentication setUserDetails(UserDetails ud) + { + try + { + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(ud, "", ud + .getAuthorities()); + auth.setDetails(ud); + auth.setAuthenticated(true); + return setCurrentAuthentication(auth); + } + catch (net.sf.acegisecurity.AuthenticationException ae) + { + throw new AuthenticationException(ae.getMessage(), ae); + } + finally + { + // Support for logging tenantdomain / username (via log4j NDC) + AuthenticationUtil.logNDC(ud.getUsername()); + } + } + + public Authentication setSystemUserAsCurrentUser() + { + return setSystemUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); + } + + public Authentication setSystemUserAsCurrentUser(String tenantDomain) + { + GrantedAuthority[] gas = new GrantedAuthority[1]; + gas[0] = new GrantedAuthorityImpl("ROLE_SYSTEM"); + return setUserDetails(new User(getSystemUserName(tenantDomain), "", true, true, true, true, gas)); + } + + public String getSystemUserName() + { + return AuthenticationUtil.SYSTEM_USER_NAME; + } + + public String getSystemUserName(String tenantDomain) + { + return this.tenantService.getDomainUser(getSystemUserName(), tenantDomain); + } + + public boolean isSystemUserName(String userName) + { + return getSystemUserName().equals(this.tenantService.getBaseNameUser(userName)); + } + + public String getGuestUserName(String tenantDomain) + { + return this.tenantService.getDomainUser(getGuestUserName(), tenantDomain); + } + + public String getGuestUserName() + { + return PermissionService.GUEST_AUTHORITY.toLowerCase(); + } + + public boolean isGuestUserName(String userName) + { + return PermissionService.GUEST_AUTHORITY.equalsIgnoreCase(this.tenantService.getBaseNameUser(userName)); + } + + public Authentication setCurrentAuthentication(Authentication authentication) + { + return AuthenticationUtil.setFullAuthentication(authentication); + } + + public Authentication getCurrentAuthentication() throws AuthenticationException + { + return AuthenticationUtil.getFullAuthentication(); + } + + public String getCurrentUserName() throws AuthenticationException + { + return AuthenticationUtil.getFullyAuthenticatedUser(); + } + + public void clearCurrentSecurityContext() + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + + public String getUserDomain(String userName) + { + return this.tenantService.getUserDomain(userName); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java index 49e47b4280..3ac9c7e377 100644 --- a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationComponentImpl.java @@ -26,13 +26,10 @@ package org.alfresco.repo.security.authentication; import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.TreeSet; import net.sf.acegisecurity.Authentication; import org.alfresco.service.Managed; -import org.alfresco.service.cmr.security.PermissionService; /** * A chaining authentication component is required for all the beans that qire up an authentication component and not an @@ -41,7 +38,7 @@ import org.alfresco.service.cmr.security.PermissionService; * * @author andyh */ -public class ChainingAuthenticationComponentImpl implements AuthenticationComponent +public class ChainingAuthenticationComponentImpl extends AbstractAuthenticationComponent { /** * NLTM authentication mode - if unset - finds the first component that supports NTLM - if set - finds the first @@ -67,7 +64,7 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon */ public List getAuthenticationComponents() { - return authenticationComponents; + return this.authenticationComponents; } /** @@ -75,7 +72,7 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon * * @param authenticationComponents */ - @Managed(category="Security") + @Managed(category = "Security") public void setAuthenticationComponents(List authenticationComponents) { this.authenticationComponents = authenticationComponents; @@ -88,7 +85,7 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon */ public AuthenticationComponent getMutableAuthenticationComponent() { - return mutableAuthenticationComponent; + return this.mutableAuthenticationComponent; } /** @@ -96,15 +93,13 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon * * @param mutableAuthenticationComponent */ - @Managed(category="Security") + @Managed(category = "Security") public void setMutableAuthenticationComponent(AuthenticationComponent mutableAuthenticationComponent) { this.mutableAuthenticationComponent = mutableAuthenticationComponent; } - - - @Managed(category="Security") + @Managed(category = "Security") public void setNtlmMode(NTLMMode ntlmMode) { this.ntlmMode = ntlmMode; @@ -113,7 +108,8 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon /** * Chain authentication with user name and password - tries all in order until one works, or fails. */ - public void authenticate(String userName, char[] password) throws AuthenticationException + @Override + protected void authenticateImpl(String userName, char[] password) { for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) { @@ -134,11 +130,12 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon * NTLM passthrough authentication - if a mode is defined - the first PASS_THROUGH provider is used - if not, the * first component that supports NTLM is used if it supports PASS_THROUGH */ + @Override public Authentication authenticate(Authentication token) throws AuthenticationException { - if (ntlmMode != null) + if (this.ntlmMode != null) { - switch (ntlmMode) + switch (this.ntlmMode) { case NONE: throw new AuthenticationException("NTLM is not supported"); @@ -169,7 +166,8 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon } else { - throw new AuthenticationException("The first authentication component to support NTLM supports MD4 hashing"); + throw new AuthenticationException( + "The first authentication component to support NTLM supports MD4 hashing"); } } } @@ -178,46 +176,15 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon } - /** - * Clear the security context - */ - public void clearCurrentSecurityContext() - { - AuthenticationUtil.clearCurrentSecurityContext(); - } - - /** - * Get the current authentication - */ - public Authentication getCurrentAuthentication() throws AuthenticationException - { - return AuthenticationUtil.getFullAuthentication(); - } - - /** - * Get the current user name - */ - public String getCurrentUserName() throws AuthenticationException - { - return AuthenticationUtil.getFullyAuthenticatedUser(); - } - - /** - * Get the guest user name - */ - public String getGuestUserName() - { - return PermissionService.GUEST_AUTHORITY.toLowerCase(); - } - /** * Get the MD4 password hash */ + @Override public String getMD4HashedPassword(String userName) { - if (ntlmMode != null) + if (this.ntlmMode != null) { - switch (ntlmMode) + switch (this.ntlmMode) { case NONE: throw new AuthenticationException("NTLM is not supported"); @@ -244,7 +211,8 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon { if (authComponent.getNTLMMode() == NTLMMode.PASS_THROUGH) { - throw new AuthenticationException("The first authentication component to support NTLM supports passthrough"); + throw new AuthenticationException( + "The first authentication component to support NTLM supports passthrough"); } else { @@ -260,11 +228,12 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon /** * Get the NTLM mode - this is only what is set if one of the implementations provides support for that mode. */ + @Override public NTLMMode getNTLMMode() { - if (ntlmMode != null) + if (this.ntlmMode != null) { - switch (ntlmMode) + switch (this.ntlmMode) { case NONE: return NTLMMode.NONE; @@ -303,33 +272,11 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon } } - /** - * Get the system user name - */ - public String getSystemUserName() - { - return AuthenticationUtil.SYSTEM_USER_NAME; - } - - /** - * If any implementation supports System then System is allowed - */ - public boolean isSystemUserName(String userName) - { - for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) - { - if (authComponent.isSystemUserName(userName)) - { - return true; - } - } - return false; - } - /** * If any implementation supports guest then guest is allowed */ - public boolean guestUserAuthenticationAllowed() + @Override + protected boolean implementationAllowsGuestLogin() { for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) { @@ -341,16 +288,7 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon return false; } - /** - * Ste the current authentication - */ - public Authentication setCurrentAuthentication(Authentication authentication) - { - return AuthenticationUtil.setFullAuthentication(authentication); - } - - - + @Override public Authentication setCurrentUser(String userName, UserNameValidationMode validationMode) { for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) @@ -370,6 +308,7 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon /** * Set the current user - try all implementations - as some may check the user exists */ + @Override public Authentication setCurrentUser(String userName) { for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) @@ -386,33 +325,6 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon throw new AuthenticationException("Failed to set current user " + userName); } - /** - * Authenticate as guest - try all in the cahin - */ - public Authentication setGuestUserAsCurrentUser() - { - for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) - { - try - { - return authComponent.setGuestUserAsCurrentUser(); - } - catch (AuthenticationException e) - { - // Ignore and chain - } - } - throw new AuthenticationException("Guest authentication is not allowed"); - } - - /** - * Set the system user - */ - public Authentication setSystemUserAsCurrentUser() - { - return setCurrentUser(getSystemUserName()); - } - /** * Helper to get authentication components * @@ -420,33 +332,20 @@ public class ChainingAuthenticationComponentImpl implements AuthenticationCompon */ private List getUsableAuthenticationComponents() { - if (mutableAuthenticationComponent == null) + if (this.mutableAuthenticationComponent == null) { - return authenticationComponents; + return this.authenticationComponents; } else { - ArrayList services = new ArrayList(authenticationComponents == null ? 1 : (authenticationComponents.size() + 1)); - services.add(mutableAuthenticationComponent); - if (authenticationComponents != null) + ArrayList services = new ArrayList( + this.authenticationComponents == null ? 1 : this.authenticationComponents.size() + 1); + services.add(this.mutableAuthenticationComponent); + if (this.authenticationComponents != null) { - services.addAll(authenticationComponents); + services.addAll(this.authenticationComponents); } return services; } } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.security.authentication.AuthenticationComponent#getDefaultAdministratorUserNames() - */ - public Set getDefaultAdministratorUserNames() - { - Set defaultAdministratorUserNames = new TreeSet(); - for (AuthenticationComponent authComponent : getUsableAuthenticationComponents()) - { - defaultAdministratorUserNames.addAll(authComponent.getDefaultAdministratorUserNames()); - } - return defaultAdministratorUserNames; - } } diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java index 52314fc861..40f5fd398e 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -1,387 +1,381 @@ -/* - * 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.repo.security.authority; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * The default implementation of the authority service. - * - * @author Andy Hind - */ -public class AuthorityServiceImpl implements AuthorityService, InitializingBean -{ - private static Log logger = LogFactory.getLog(AuthorityServiceImpl.class); - - private PersonService personService; - - private NodeService nodeService; - - private TenantService tenantService; - - private AuthorityDAO authorityDAO; - - private AuthenticationService authenticationService; - - private PermissionServiceSPI permissionServiceSPI; - - private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); - - private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); - - private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); - - private Set adminGroups = Collections.emptySet(); - - public AuthorityServiceImpl() - { - super(); - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) - { - this.permissionServiceSPI = permissionServiceSPI; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - logger.warn("Bean property 'authenticationService' no longer required on 'AuthorityServiceImpl'."); - } - - public void setAdminGroups(Set adminGroups) - { - this.adminGroups = adminGroups; - } - - /* (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() throws Exception - { - // Fully qualify the admin group names - if (!this.adminGroups.isEmpty()) - { - Set adminGroups = new HashSet(this.adminGroups.size()); - for (String group : this.adminGroups) - { - adminGroups.add(getName(AuthorityType.GROUP, group)); - } - this.adminGroups = adminGroups; - } - } - - public boolean hasAdminAuthority() - { - String currentUserName = AuthenticationUtil.getRunAsUser(); - - // Determine whether the administrator role is mapped to this user or one of their groups - return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY)); - } - - public boolean isAdminAuthority(String authorityName) - { - String canonicalName = personService.getUserIdentifier(authorityName); - if (canonicalName == null) - { - canonicalName = authorityName; - } - - // Determine whether the administrator role is mapped to this user or one of their groups - return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY); - } - - public Set getAuthorities() - { - String currentUserName = AuthenticationUtil.getRunAsUser(); - return getAuthoritiesForUser(currentUserName); - } - - public Set getAuthoritiesForUser(String currentUserName) - { - Set authorities = new HashSet(); - - authorities.addAll(getContainingAuthorities(null, currentUserName, false)); - - // Work out mapped roles - - // Check named admin users - Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames(); - - // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will - // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, - // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority - String currentUserBaseName = tenantService.getBaseNameUser(currentUserName); - boolean isAdminUser = (adminUsers.contains(currentUserName) || adminUsers.contains(currentUserBaseName)); - - // Check named admin groups - if (!isAdminUser && !adminGroups.isEmpty()) - { - for (String authority : authorities) - { - if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority))) - { - isAdminUser = true; - break; - } - } - } - - if (isAdminUser) - { - authorities.addAll(adminSet); - } - if (AuthorityType.getAuthorityType(currentUserBaseName) != AuthorityType.GUEST) - { - authorities.addAll(allSet); - } - return authorities; - } - - public Set getAllAuthorities(AuthorityType type) - { - Set authorities = new HashSet(); - switch (type) - { - case ADMIN: - authorities.addAll(adminSet); - break; - case EVERYONE: - authorities.addAll(allSet); - break; - case GUEST: - authorities.addAll(guestSet); - break; - case GROUP: - authorities.addAll(authorityDAO.getAllAuthorities(type)); - break; - case OWNER: - break; - case ROLE: - authorities.addAll(authorityDAO.getAllAuthorities(type)); - break; - case USER: - for (NodeRef personRef : personService.getAllPeople()) - { - authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, - ContentModel.PROP_USERNAME))); - } - break; - default: - break; - } - return authorities; - } - - - - public Set findAuthorities(AuthorityType type, String namePattern) - { - Set authorities = new HashSet(); - switch (type) - { - case ADMIN: - case EVERYONE: - case GUEST: - throw new UnsupportedOperationException(); - case GROUP: - authorities.addAll(authorityDAO.findAuthorities(type, namePattern)); - break; - case OWNER: - case ROLE: - throw new UnsupportedOperationException(); - case USER: - throw new UnsupportedOperationException(); - default: - break; - } - return authorities; - } - - public void addAuthority(String parentName, String childName) - { - if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER)) - { - if(!personService.personExists(childName)) - { - throw new AuthorityException("The person "+childName+" does not exist and can not be added to a group"); - } - } - authorityDAO.addAuthority(parentName, childName); - } - - private void checkTypeIsMutable(AuthorityType type) - { - if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE)) - { - return; - } - else - { - throw new AuthorityException("Trying to modify a fixed authority"); - } - } - - public String createAuthority(AuthorityType type, String parentName, String shortName) - { - return createAuthority(type, parentName, shortName, shortName); - } - - public void deleteAuthority(String name) - { - AuthorityType type = AuthorityType.getAuthorityType(name); - checkTypeIsMutable(type); - authorityDAO.deleteAuthority(name); - permissionServiceSPI.deletePermissions(name); - } - - public Set getAllRootAuthorities(AuthorityType type) - { - return authorityDAO.getAllRootAuthorities(type); - } - - public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) - { - return authorityDAO.getContainedAuthorities(type, name, immediate); - } - - public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) - { - return authorityDAO.getContainingAuthorities(type, name, immediate); - } - - public String getName(AuthorityType type, String shortName) - { - if (type.isFixedString()) - { - return type.getFixedString(); - } - else if (type.isPrefixed()) - { - return type.getPrefixString() + shortName; - } - else - { - return shortName; - } - } - - public String getShortName(String name) - { - AuthorityType type = AuthorityType.getAuthorityType(name); - if (type.isFixedString()) - { - return ""; - } - else if (type.isPrefixed()) - { - return name.substring(type.getPrefixString().length()); - } - else - { - return name; - } - - } - - public void removeAuthority(String parentName, String childName) - { - authorityDAO.removeAuthority(parentName, childName); - } - - public boolean authorityExists(String name) - { - return authorityDAO.authorityExists(name); - } - - public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName) - { - checkTypeIsMutable(type); - String name = getName(type, shortName); - authorityDAO.createAuthority(parentName, name, authorityDisplayName); - return name; - } - - public String getAuthorityDisplayName(String name) - { - String displayName = authorityDAO.getAuthorityDisplayName(name); - if(displayName == null) - { - displayName = getShortName(name); - } - return displayName; - } - - public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) - { - AuthorityType type = AuthorityType.getAuthorityType(authorityName); - checkTypeIsMutable(type); - authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName); - } - -} +/* + * 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.repo.security.authority; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * The default implementation of the authority service. + * + * @author Andy Hind + */ +public class AuthorityServiceImpl implements AuthorityService, InitializingBean +{ + private static Log logger = LogFactory.getLog(AuthorityServiceImpl.class); + + private PersonService personService; + + private NodeService nodeService; + + private TenantService tenantService; + + private AuthorityDAO authorityDAO; + + private AuthenticationService authenticationService; + + private PermissionServiceSPI permissionServiceSPI; + + private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); + + private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); + + private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); + + private Set adminGroups = Collections.emptySet(); + + public AuthorityServiceImpl() + { + super(); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) + { + this.permissionServiceSPI = permissionServiceSPI; + } + + public void setAdminGroups(Set adminGroups) + { + this.adminGroups = adminGroups; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // Fully qualify the admin group names + if (!this.adminGroups.isEmpty()) + { + Set adminGroups = new HashSet(this.adminGroups.size()); + for (String group : this.adminGroups) + { + adminGroups.add(getName(AuthorityType.GROUP, group)); + } + this.adminGroups = adminGroups; + } + } + + public boolean hasAdminAuthority() + { + String currentUserName = AuthenticationUtil.getRunAsUser(); + + // Determine whether the administrator role is mapped to this user or one of their groups + return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY)); + } + + public boolean isAdminAuthority(String authorityName) + { + String canonicalName = personService.getUserIdentifier(authorityName); + if (canonicalName == null) + { + canonicalName = authorityName; + } + + // Determine whether the administrator role is mapped to this user or one of their groups + return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY); + } + + public Set getAuthorities() + { + String currentUserName = AuthenticationUtil.getRunAsUser(); + return getAuthoritiesForUser(currentUserName); + } + + public Set getAuthoritiesForUser(String currentUserName) + { + Set authorities = new HashSet(); + + authorities.addAll(getContainingAuthorities(null, currentUserName, false)); + + // Work out mapped roles + + // Check named admin users + Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames(); + + // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will + // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, + // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority + String currentUserBaseName = tenantService.getBaseNameUser(currentUserName); + boolean isAdminUser = (adminUsers.contains(currentUserName) || adminUsers.contains(currentUserBaseName)); + + // Check named admin groups + if (!isAdminUser && !adminGroups.isEmpty()) + { + for (String authority : authorities) + { + if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority))) + { + isAdminUser = true; + break; + } + } + } + + if (isAdminUser) + { + authorities.addAll(adminSet); + } + if (AuthorityType.getAuthorityType(currentUserBaseName) != AuthorityType.GUEST) + { + authorities.addAll(allSet); + } + return authorities; + } + + public Set getAllAuthorities(AuthorityType type) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + authorities.addAll(adminSet); + break; + case EVERYONE: + authorities.addAll(allSet); + break; + case GUEST: + authorities.addAll(guestSet); + break; + case GROUP: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case OWNER: + break; + case ROLE: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case USER: + for (NodeRef personRef : personService.getAllPeople()) + { + authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, + ContentModel.PROP_USERNAME))); + } + break; + default: + break; + } + return authorities; + } + + + + public Set findAuthorities(AuthorityType type, String namePattern) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + case EVERYONE: + case GUEST: + throw new UnsupportedOperationException(); + case GROUP: + authorities.addAll(authorityDAO.findAuthorities(type, namePattern)); + break; + case OWNER: + case ROLE: + throw new UnsupportedOperationException(); + case USER: + throw new UnsupportedOperationException(); + default: + break; + } + return authorities; + } + + public void addAuthority(String parentName, String childName) + { + if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER)) + { + if(!personService.personExists(childName)) + { + throw new AuthorityException("The person "+childName+" does not exist and can not be added to a group"); + } + } + authorityDAO.addAuthority(parentName, childName); + } + + private void checkTypeIsMutable(AuthorityType type) + { + if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE)) + { + return; + } + else + { + throw new AuthorityException("Trying to modify a fixed authority"); + } + } + + public String createAuthority(AuthorityType type, String parentName, String shortName) + { + return createAuthority(type, parentName, shortName, shortName); + } + + public void deleteAuthority(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + checkTypeIsMutable(type); + authorityDAO.deleteAuthority(name); + permissionServiceSPI.deletePermissions(name); + } + + public Set getAllRootAuthorities(AuthorityType type) + { + return authorityDAO.getAllRootAuthorities(type); + } + + public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainedAuthorities(type, name, immediate); + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainingAuthorities(type, name, immediate); + } + + public String getName(AuthorityType type, String shortName) + { + if (type.isFixedString()) + { + return type.getFixedString(); + } + else if (type.isPrefixed()) + { + return type.getPrefixString() + shortName; + } + else + { + return shortName; + } + } + + public String getShortName(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + if (type.isFixedString()) + { + return ""; + } + else if (type.isPrefixed()) + { + return name.substring(type.getPrefixString().length()); + } + else + { + return name; + } + + } + + public void removeAuthority(String parentName, String childName) + { + authorityDAO.removeAuthority(parentName, childName); + } + + public boolean authorityExists(String name) + { + return authorityDAO.authorityExists(name); + } + + public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName) + { + checkTypeIsMutable(type); + String name = getName(type, shortName); + authorityDAO.createAuthority(parentName, name, authorityDisplayName); + return name; + } + + public String getAuthorityDisplayName(String name) + { + String displayName = authorityDAO.getAuthorityDisplayName(name); + if(displayName == null) + { + displayName = getShortName(name); + } + return displayName; + } + + public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) + { + AuthorityType type = AuthorityType.getAuthorityType(authorityName); + checkTypeIsMutable(type); + authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName); + } + +} diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java index 8e1ced1866..f11d5e0159 100644 --- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java @@ -1,287 +1,287 @@ -/* - * 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.repo.security.authority; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; - -/** - * The default implementation of the authority service. - * - * @author Andy Hind - */ -public class SimpleAuthorityServiceImpl implements AuthorityService -{ - private PersonService personService; - - private NodeService nodeService; - - private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); - - private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); - - private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); - - private Set adminUsers; - - private AuthenticationComponent authenticationComponent; - - private TenantService tenantService; - - - public SimpleAuthorityServiceImpl() - { - super(); - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - - /** - * Currently the admin authority is granted only to the ALFRESCO_ADMIN_USER - * user. - */ - public boolean hasAdminAuthority() - { - String currentUserName = authenticationComponent.getCurrentUserName(); - - // note: for MT, this currently relies on a naming convention which assumes that all tenant admins will - // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, - // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority - - return ((currentUserName != null) && (adminUsers.contains(currentUserName) || adminUsers.contains(tenantService.getBaseNameUser(currentUserName)))); - } - - /* (non-Javadoc) - * @see org.alfresco.service.cmr.security.AuthorityService#isAdminAuthority(java.lang.String) - */ - public boolean isAdminAuthority(String authorityName) - { - String canonicalName = personService.getUserIdentifier(authorityName); - if (canonicalName == null) - { - canonicalName = authorityName; - } - return adminUsers.contains(canonicalName); - } - - // IOC - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setAdminUsers(Set adminUsers) - { - this.adminUsers = adminUsers; - } - - public Set getAuthorities() - { - Set authorities = new HashSet(); - String currentUserName = authenticationComponent.getCurrentUserName(); - if (adminUsers.contains(currentUserName)) - { - authorities.addAll(adminSet); - } - if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST) - { - authorities.addAll(allSet); - } - return authorities; - } - - public Set getAllAuthorities(AuthorityType type) - { - Set authorities = new HashSet(); - switch (type) - { - case ADMIN: - authorities.addAll(adminSet); - break; - case EVERYONE: - authorities.addAll(allSet); - break; - case GUEST: - authorities.addAll(guestSet); - break; - case GROUP: - authorities.addAll(allSet); - break; - case OWNER: - break; - case ROLE: - break; - case USER: - for (NodeRef personRef : personService.getAllPeople()) - { - authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, - ContentModel.PROP_USERNAME))); - } - break; - default: - break; - } - return authorities; - } - - - - public Set findAuthorities(AuthorityType type, String namePattern) - { - return Collections.emptySet(); - } - - public void addAuthority(String parentName, String childName) - { - - } - - - public String createAuthority(AuthorityType type, String parentName, String shortName) - { - return ""; - } - - public void deleteAuthority(String name) - { - - } - - public Set getAllRootAuthorities(AuthorityType type) - { - return getAllAuthorities(type); - } - - public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) - { - return Collections.emptySet(); - } - - public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) - { - return Collections.emptySet(); - } - - public String getName(AuthorityType type, String shortName) - { - if (type.isFixedString()) - { - return type.getFixedString(); - } - else if (type.isPrefixed()) - { - return type.getPrefixString() + shortName; - } - else - { - return shortName; - } - } - - public String getShortName(String name) - { - AuthorityType type = AuthorityType.getAuthorityType(name); - if (type.isFixedString()) - { - return ""; - } - else if (type.isPrefixed()) - { - return name.substring(type.getPrefixString().length()); - } - else - { - return name; - } - - } - - public void removeAuthority(String parentName, String childName) - { - - } - - public boolean authorityExists(String name) - { - return false; - } - - public Set getAuthoritiesForUser(String currentUserName) - { - Set authorities = new HashSet(); - if (adminUsers.contains(currentUserName)) - { - authorities.addAll(adminSet); - } - if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST) - { - authorities.addAll(allSet); - } - return authorities; - } - - public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName) - { - return ""; - } - - public String getAuthorityDisplayName(String name) - { - return ""; - } - - public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) - { - - } - -} +/* + * 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.repo.security.authority; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; + +/** + * The default implementation of the authority service. + * + * @author Andy Hind + */ +public class SimpleAuthorityServiceImpl implements AuthorityService +{ + private PersonService personService; + + private NodeService nodeService; + + private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); + + private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); + + private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); + + private Set adminUsers; + + private AuthenticationContext authenticationContext; + + private TenantService tenantService; + + + public SimpleAuthorityServiceImpl() + { + super(); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + + /** + * Currently the admin authority is granted only to the ALFRESCO_ADMIN_USER + * user. + */ + public boolean hasAdminAuthority() + { + String currentUserName = authenticationContext.getCurrentUserName(); + + // note: for MT, this currently relies on a naming convention which assumes that all tenant admins will + // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, + // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority + + return ((currentUserName != null) && (adminUsers.contains(currentUserName) || adminUsers.contains(tenantService.getBaseNameUser(currentUserName)))); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.security.AuthorityService#isAdminAuthority(java.lang.String) + */ + public boolean isAdminAuthority(String authorityName) + { + String canonicalName = personService.getUserIdentifier(authorityName); + if (canonicalName == null) + { + canonicalName = authorityName; + } + return adminUsers.contains(canonicalName); + } + + // IOC + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setAdminUsers(Set adminUsers) + { + this.adminUsers = adminUsers; + } + + public Set getAuthorities() + { + Set authorities = new HashSet(); + String currentUserName = authenticationContext.getCurrentUserName(); + if (adminUsers.contains(currentUserName)) + { + authorities.addAll(adminSet); + } + if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST) + { + authorities.addAll(allSet); + } + return authorities; + } + + public Set getAllAuthorities(AuthorityType type) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + authorities.addAll(adminSet); + break; + case EVERYONE: + authorities.addAll(allSet); + break; + case GUEST: + authorities.addAll(guestSet); + break; + case GROUP: + authorities.addAll(allSet); + break; + case OWNER: + break; + case ROLE: + break; + case USER: + for (NodeRef personRef : personService.getAllPeople()) + { + authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, + ContentModel.PROP_USERNAME))); + } + break; + default: + break; + } + return authorities; + } + + + + public Set findAuthorities(AuthorityType type, String namePattern) + { + return Collections.emptySet(); + } + + public void addAuthority(String parentName, String childName) + { + + } + + + public String createAuthority(AuthorityType type, String parentName, String shortName) + { + return ""; + } + + public void deleteAuthority(String name) + { + + } + + public Set getAllRootAuthorities(AuthorityType type) + { + return getAllAuthorities(type); + } + + public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) + { + return Collections.emptySet(); + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + return Collections.emptySet(); + } + + public String getName(AuthorityType type, String shortName) + { + if (type.isFixedString()) + { + return type.getFixedString(); + } + else if (type.isPrefixed()) + { + return type.getPrefixString() + shortName; + } + else + { + return shortName; + } + } + + public String getShortName(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + if (type.isFixedString()) + { + return ""; + } + else if (type.isPrefixed()) + { + return name.substring(type.getPrefixString().length()); + } + else + { + return name; + } + + } + + public void removeAuthority(String parentName, String childName) + { + + } + + public boolean authorityExists(String name) + { + return false; + } + + public Set getAuthoritiesForUser(String currentUserName) + { + Set authorities = new HashSet(); + if (adminUsers.contains(currentUserName)) + { + authorities.addAll(adminSet); + } + if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST) + { + authorities.addAll(allSet); + } + return authorities; + } + + public String createAuthority(AuthorityType type, String parentName, String shortName, String authorityDisplayName) + { + return ""; + } + + public String getAuthorityDisplayName(String name) + { + return ""; + } + + public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) + { + + } + +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index cb9d49f161..7df41d6fa7 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -1,1946 +1,1937 @@ -/* - * 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.repo.security.permissions.impl; - -import java.io.Serializable; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import net.sf.acegisecurity.Authentication; -import net.sf.acegisecurity.GrantedAuthority; -import net.sf.acegisecurity.providers.dao.User; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.avm.AVMRepository; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.security.permissions.ACLType; -import org.alfresco.repo.security.permissions.AccessControlEntry; -import org.alfresco.repo.security.permissions.AccessControlList; -import org.alfresco.repo.security.permissions.AccessControlListProperties; -import org.alfresco.repo.security.permissions.DynamicAuthority; -import org.alfresco.repo.security.permissions.NodePermissionEntry; -import org.alfresco.repo.security.permissions.PermissionEntry; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.repo.version.common.VersionUtil; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -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.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.PermissionContext; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.EqualsHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * The Alfresco implementation of a permissions service against our APIs for the permissions model and permissions - * persistence. - * - * @author andyh - */ -public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean -{ - - static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference(QName.createQName("", PermissionService.ALL_PERMISSIONS), - PermissionService.ALL_PERMISSIONS); - - private static Log log = LogFactory.getLog(PermissionServiceImpl.class); - - /** a transactionally-safe cache to be injected */ - private SimpleCache accessCache; - - /* - * Access to the model - */ - private ModelDAO modelDAO; - - /* - * Access to permissions - */ - private PermissionsDaoComponent permissionsDaoComponent; - - /* - * Access to the node service - */ - private NodeService nodeService; - - /* - * Access to the tenant service - */ - private TenantService tenantService; - - /* - * Access to the data dictionary - */ - private DictionaryService dictionaryService; - - /* - * Access to the authority component - */ - private AuthorityService authorityService; - - /* - * Dynamic authorities providers - */ - private List dynamicAuthorities; - - private PolicyComponent policyComponent; - - private AclDaoComponent aclDaoComponent; - - private PermissionReference allPermissionReference; - - /** - * Standard spring construction. - */ - public PermissionServiceImpl() - { - super(); - } - - // - // Inversion of control - // - - /** - * Set the dictionary service - * @param dictionaryService - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the permissions model dao - * - * @param modelDAO - */ - public void setModelDAO(ModelDAO modelDAO) - { - this.modelDAO = modelDAO; - } - - /** - * Set the node service. - * - * @param nodeService - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the tenant service. - * @param tenantService - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - /** - * Set the permissions dao component - * - * @param permissionsDaoComponent - */ - public void setPermissionsDaoComponent(PermissionsDaoComponent permissionsDaoComponent) - { - this.permissionsDaoComponent = permissionsDaoComponent; - } - - /** - * @deprecated - */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - log.warn("Bean property 'authenticationComponent' no longer required for 'PermissionServiceImpl'."); - } - - /** - * Set the authority service. - * - * @param authorityService - */ - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - /** - * Set the dynamic authorities - * - * @param dynamicAuthorities - */ - public void setDynamicAuthorities(List dynamicAuthorities) - { - this.dynamicAuthorities = dynamicAuthorities; - } - - /** - * Set the ACL DAO component. - * - * @param aclDaoComponent - */ - public void setAclDaoComponent(AclDaoComponent aclDaoComponent) - { - this.aclDaoComponent = aclDaoComponent; - } - - /** - * Set the permissions access cache. - * - * @param accessCache - * a transactionally safe cache - */ - public void setAccessCache(SimpleCache accessCache) - { - this.accessCache = accessCache; - } - - /** - * Set the policy component - * - * @param policyComponent - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - /** - * Cache clear on move node - * - * @param oldChildAssocRef - * @param newChildAssocRef - */ - public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) - { - accessCache.clear(); - } - - public void afterPropertiesSet() throws Exception - { - if (dictionaryService == null) - { - throw new IllegalArgumentException("Property 'dictionaryService' has not been set"); - } - if (modelDAO == null) - { - throw new IllegalArgumentException("Property 'modelDAO' has not been set"); - } - if (nodeService == null) - { - throw new IllegalArgumentException("Property 'nodeService' has not been set"); - } - if (permissionsDaoComponent == null) - { - throw new IllegalArgumentException("Property 'permissionsDAO' has not been set"); - } - if (authorityService == null) - { - throw new IllegalArgumentException("Property 'authorityService' has not been set"); - } - if (accessCache == null) - { - throw new IllegalArgumentException("Property 'accessCache' has not been set"); - } - if (policyComponent == null) - { - throw new IllegalArgumentException("Property 'policyComponent' has not been set"); - } - if (aclDaoComponent == null) - { - throw new IllegalArgumentException("Property 'aclDaoComponent' has not been set"); - } - - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); - - allPermissionReference = getPermissionReference(ALL_PERMISSIONS); - - } - - // - // Permissions Service - // - - public String getOwnerAuthority() - { - return OWNER_AUTHORITY; - } - - public String getAllAuthorities() - { - return ALL_AUTHORITIES; - } - - public String getAllPermission() - { - return ALL_PERMISSIONS; - } - - public Set getPermissions(NodeRef nodeRef) - { - return getAllPermissionsImpl(nodeRef, true, true); - } - - public Set getAllSetPermissions(NodeRef nodeRef) - { - HashSet accessPermissions = new HashSet(); - NodePermissionEntry nodePremissionEntry = getSetPermissions(nodeRef); - for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe.getAccessStatus(), pe.getAuthority(), pe.getPosition())); - } - return accessPermissions; - } - - public Set getAllSetPermissions(StoreRef storeRef) - { - HashSet accessPermissions = new HashSet(); - NodePermissionEntry nodePremissionEntry = getSetPermissions(storeRef); - for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe.getAccessStatus(), pe.getAuthority(), pe.getPosition())); - } - return accessPermissions; - } - - private Set getAllPermissionsImpl(NodeRef nodeRef, boolean includeTrue, boolean includeFalse) - { - String userName = AuthenticationUtil.getRunAsUser(); - HashSet accessPermissions = new HashSet(); - for (PermissionReference pr : getSettablePermissionReferences(nodeRef)) - { - if (hasPermission(nodeRef, pr) == AccessStatus.ALLOWED) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.ALLOWED, userName, -1)); - } - else - { - if (includeFalse) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.DENIED, userName, -1)); - } - } - } - return accessPermissions; - } - - public Set getSettablePermissions(NodeRef nodeRef) - { - Set settable = getSettablePermissionReferences(nodeRef); - Set strings = new HashSet(settable.size()); - for (PermissionReference pr : settable) - { - strings.add(getPermission(pr)); - } - return strings; - } - - public Set getSettablePermissions(QName type) - { - Set settable = getSettablePermissionReferences(type); - Set strings = new LinkedHashSet(settable.size()); - for (PermissionReference pr : settable) - { - strings.add(getPermission(pr)); - } - return strings; - } - - public NodePermissionEntry getSetPermissions(NodeRef nodeRef) - { - return permissionsDaoComponent.getPermissions(tenantService.getName(nodeRef)); - } - - public NodePermissionEntry getSetPermissions(StoreRef storeRef) - { - return permissionsDaoComponent.getPermissions(storeRef); - } - - public AccessStatus hasPermission(NodeRef passedNodeRef, final PermissionReference permIn) - { - // If the node ref is null there is no sensible test to do - and there - // must be no permissions - // - so we allow it - if (passedNodeRef == null) - { - return AccessStatus.ALLOWED; - } - - // If the permission is null we deny - if (permIn == null) - { - return AccessStatus.DENIED; - } - - // AVM nodes - test for existence underneath - if (passedNodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) - { - return doAvmCan(passedNodeRef, permIn); - } - - // Allow permissions for nodes that do not exist - if (!nodeService.exists(passedNodeRef)) - { - return AccessStatus.ALLOWED; - } - - // Because of VersionedNodeRef has no any inherited from source Frozen NodeRef permissions (it has only default permissions), - // it is necessary to avoid cases when some user without appropriate permissions trying to receive any resource from its any version link etc. - // That could be proceed through receiving Frozen NodeRef instance for this VersionedNodeRef instance. There is appears a possibility to get - // access to specified for Frozen NodeRef instance permissions - - // NOTE: maybe in future there will appear situation when changing Node permissions will be a cause for creating new Node version. In other words, - // VersionedNodeRefs will contain their own permissions (whose, probably, will differ from version to version). In this case you should delete/comment this code!!! - if (isVersionedNodeRefInstance(passedNodeRef)) - { - passedNodeRef = convertVersionedNodeRefToFrozenNodeRef(VersionUtil.convertNodeRef(passedNodeRef)); - } - - final NodeRef nodeRef = tenantService.getName(passedNodeRef); - - final PermissionReference perm; - if (permIn.equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - perm = getAllPermissionReference(); - } - else - { - perm = permIn; - } - - if (AuthenticationUtil.getRunAsUser() == null) - { - return AccessStatus.DENIED; - } - - if (AuthenticationUtil.isRunAsUserTheSystemUser()) - { - return AccessStatus.ALLOWED; - } - - // New ACLs - - AccessControlListProperties properties = permissionsDaoComponent.getAccessControlListProperties(nodeRef); - if ((properties != null) && (properties.getAclType() != null) && (properties.getAclType() != ACLType.OLD)) - { - QName typeQname = nodeService.getType(nodeRef); - Set aspectQNames = nodeService.getAspects(nodeRef); - PermissionContext context = new PermissionContext(typeQname); - context.getAspects().addAll(aspectQNames); - Authentication auth = AuthenticationUtil.getRunAsAuthentication(); - String user = AuthenticationUtil.getRunAsUser(); - for (String dynamicAuthority : getDynamicAuthorities(auth, nodeRef, perm)) - { - context.addDynamicAuthorityAssignment(user, dynamicAuthority); - } - return hasPermission(properties.getId(), context, perm); - } - - // Get the current authentications - // Use the smart authentication cache to improve permissions performance - Authentication auth = AuthenticationUtil.getRunAsAuthentication(); - final Set authorisations = getAuthorisations(auth, nodeRef, perm); - - // If the node does not support the given permission there is no point - // doing the test - Set available = AuthenticationUtil.runAs(new RunAsWork>() - { - public Set doWork() throws Exception - { - return modelDAO.getAllPermissions(nodeRef); - } - - }, AuthenticationUtil.getSystemUserName()); - - available.add(getAllPermissionReference()); - available.add(OLD_ALL_PERMISSIONS_REFERENCE); - - final Serializable key = generateKey(authorisations, nodeRef, perm, CacheType.HAS_PERMISSION); - if (!(available.contains(perm))) - { - accessCache.put(key, AccessStatus.DENIED); - return AccessStatus.DENIED; - } - - if (AuthenticationUtil.isRunAsUserTheSystemUser()) - { - return AccessStatus.ALLOWED; - } - - return AuthenticationUtil.runAs(new RunAsWork() - { - - public AccessStatus doWork() throws Exception - { - - AccessStatus status = accessCache.get(key); - if (status != null) - { - return status; - } - - // - // TODO: Dynamic permissions via evaluators - // - - /* - * Does the current authentication have the supplied permission on the given node. - */ - - QName typeQname = nodeService.getType(nodeRef); - Set aspectQNames = nodeService.getAspects(nodeRef); - - NodeTest nt = new NodeTest(perm, typeQname, aspectQNames); - boolean result = nt.evaluate(authorisations, nodeRef); - if (log.isDebugEnabled()) - { - log.debug("Permission <" - + perm + "> is " + (result ? "allowed" : "denied") + " for " + AuthenticationUtil.getRunAsUser() + " on node " - + nodeService.getPath(nodeRef)); - } - - status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; - accessCache.put(key, status); - return status; - } - }, AuthenticationUtil.getSystemUserName()); - - } - - private AccessStatus doAvmCan(NodeRef nodeRef, PermissionReference permission) - { - org.alfresco.util.Pair avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); - int version = avmVersionPath.getFirst(); - String path = avmVersionPath.getSecond(); - boolean result = AVMRepository.GetInstance().can(nodeRef.getStoreRef().getIdentifier(), version, path, permission.getName()); - AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; - return status; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.service.cmr.security.PermissionService#hasPermission(java.lang.Long, java.lang.String, - * java.lang.String) - */ - public AccessStatus hasPermission(Long aclID, PermissionContext context, String permission) - { - return hasPermission(aclID, context, getPermissionReference(permission)); - } - - - private AccessStatus hasPermission(Long aclId, PermissionContext context, PermissionReference permission) - { - if (aclId == null) - { - // Enforce store ACLs if set - the AVM default was to "allow" if there are no permissions set ... - if (context.getStoreAcl() == null) - { - return AccessStatus.ALLOWED; - } - else - { - if (AuthenticationUtil.isRunAsUserTheSystemUser()) - { - return AccessStatus.ALLOWED; - } - - Authentication auth = AuthenticationUtil.getRunAsAuthentication(); - if (auth == null) - { - throw new IllegalStateException("Unauthenticated"); - } - Set storeAuthorisations = getAuthorisations(auth, (PermissionContext) null); - QName typeQname = context.getType(); - Set aspectQNames = context.getAspects(); - AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); - boolean result = aclTest.evaluate(storeAuthorisations, context.getStoreAcl(), context); - AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; - return status; - } - } - - if (permission == null) - { - return AccessStatus.DENIED; - } - - if (AuthenticationUtil.getRunAsUser() == null) - { - return AccessStatus.DENIED; - } - - if (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName())) - { - return AccessStatus.ALLOWED; - } - - // Get the current authentications - // Use the smart authentication cache to improve permissions performance - Authentication auth = AuthenticationUtil.getRunAsAuthentication(); - if (auth == null) - { - throw new IllegalStateException("Unauthenticated"); - } - - Set authorisations = getAuthorisations(auth, context); - - // If the node does not support the given permission there is no point - // doing the test - - final QName typeQname = context.getType(); - final Set aspectQNames = context.getAspects(); - - Set available = AuthenticationUtil.runAs(new RunAsWork>() - { - public Set doWork() throws Exception - { - return modelDAO.getAllPermissions(typeQname, aspectQNames); - } - - }, AuthenticationUtil.getSystemUserName()); - available.add(getAllPermissionReference()); - available.add(OLD_ALL_PERMISSIONS_REFERENCE); - - if (!(available.contains(permission))) - { - return AccessStatus.DENIED; - } - - if (AuthenticationUtil.isRunAsUserTheSystemUser()) - { - return AccessStatus.ALLOWED; - } - - if (permission.equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - permission = getAllPermissionReference(); - } - - boolean result; - if (context.getStoreAcl() == null) - { - AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); - result = aclTest.evaluate(authorisations, aclId, context); - } - else - { - Set storeAuthorisations = getAuthorisations(auth, (PermissionContext) null); - AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); - result = aclTest.evaluate(authorisations, aclId, context) && aclTest.evaluate(storeAuthorisations, context.getStoreAcl(), context); - } - AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; - return status; - - } - - /** - * Control permissions cache - only used when we do old style permission evaluations - * - which should only be in DM stores where no permissions have been set - * - * @author andyh - * - */ - enum CacheType - { - /** - * cache full check - */ - HAS_PERMISSION, - /** - * Cache single permission check - */ - SINGLE_PERMISSION, - /** - * Cache single permission check for global permission checks - */ - SINGLE_PERMISSION_GLOBAL; - } - - /** - * Key for a cache object is built from all the known Authorities (which can change dynamically so they must all be - * used) the NodeRef ID and the permission reference itself. This gives a unique key for each permission test. - */ - static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm, CacheType type) - { - LinkedHashSet key = new LinkedHashSet(); - key.add(perm.toString()); - key.addAll(auths); - key.add(nodeRef); - key.add(type); - return key; - } - - /** - * Get the authorisations for the currently authenticated user - * - * @param auth - * @return the set of authorisations - */ - private Set getAuthorisations(Authentication auth, NodeRef nodeRef, PermissionReference required) - { - - HashSet auths = new HashSet(); - // No authenticated user then no permissions - if (auth == null) - { - return auths; - } - // TODO: Refactor and use the authentication service for this. - User user = (User) auth.getPrincipal(); - - String username = user.getUsername(); - auths.add(username); - - if (tenantService.getBaseNameUser(username).equalsIgnoreCase(PermissionService.GUEST_AUTHORITY)) - { - auths.add(PermissionService.GUEST_AUTHORITY); - } - - for (GrantedAuthority authority : auth.getAuthorities()) - { - auths.add(authority.getAuthority()); - } - auths.addAll(getDynamicAuthorities(auth, nodeRef, required)); - auths.addAll(authorityService.getAuthoritiesForUser(username)); - return auths; - } - - private Set getDynamicAuthorities(Authentication auth, NodeRef nodeRef, PermissionReference required) - { - HashSet auths = new HashSet(); - - if (auth == null) - { - return auths; - } - User user = (User) auth.getPrincipal(); - String username = user.getUsername(); - - nodeRef = tenantService.getName(nodeRef); - if (nodeRef != null) - { - if (dynamicAuthorities != null) - { - for (DynamicAuthority da : dynamicAuthorities) - { - Set requiredFor = da.requiredFor(); - if ((requiredFor == null) || (requiredFor.contains(required))) - { - if (da.hasAuthority(nodeRef, username)) - { - auths.add(da.getAuthority()); - } - } - } - } - } - auths.addAll(authorityService.getAuthoritiesForUser(user.getUsername())); - return auths; - } - - private Set getAuthorisations(Authentication auth, PermissionContext context) - { - HashSet auths = new HashSet(); - // No authenticated user then no permissions - if (auth == null) - { - return auths; - } - // TODO: Refactor and use the authentication service for this. - User user = (User) auth.getPrincipal(); - auths.add(user.getUsername()); - for (GrantedAuthority authority : auth.getAuthorities()) - { - auths.add(authority.getAuthority()); - } - auths.addAll(authorityService.getAuthoritiesForUser(user.getUsername())); - - if (context != null) - { - Map> dynamicAuthorityAssignments = context.getDynamicAuthorityAssignment(); - HashSet dynAuths = new HashSet(); - for (String current : auths) - { - Set dynos = dynamicAuthorityAssignments.get(current); - if (dynos != null) - { - dynAuths.addAll(dynos); - } - } - auths.addAll(dynAuths); - } - - return auths; - } - - public NodePermissionEntry explainPermission(NodeRef nodeRef, PermissionReference perm) - { - // TODO Auto-generated method stub - return null; - } - - public void clearPermission(StoreRef storeRef, String authority) - { - permissionsDaoComponent.deletePermissions(storeRef, authority); - accessCache.clear(); - - } - - public void deletePermission(StoreRef storeRef, String authority, String perm) - { - deletePermission(storeRef, authority, getPermissionReference(perm)); - } - - - private void deletePermission(StoreRef storeRef, String authority, PermissionReference perm) - { - permissionsDaoComponent.deletePermission(storeRef, authority, perm); - accessCache.clear(); - } - - public void deletePermissions(StoreRef storeRef) - { - permissionsDaoComponent.deletePermissions(storeRef); - accessCache.clear(); - - } - - public void setPermission(StoreRef storeRef, String authority, String perm, boolean allow) - { - setPermission(storeRef, authority, getPermissionReference(perm), allow); - } - - private void setPermission(StoreRef storeRef, String authority, PermissionReference permission, boolean allow) - { - permissionsDaoComponent.setPermission(storeRef, authority, permission, allow); - accessCache.clear(); - - } - - public void deletePermissions(NodeRef nodeRef) - { - permissionsDaoComponent.deletePermissions(tenantService.getName(nodeRef)); - accessCache.clear(); - } - - public void deletePermissions(NodePermissionEntry nodePermissionEntry) - { - permissionsDaoComponent.deletePermissions(tenantService.getName(nodePermissionEntry.getNodeRef())); - accessCache.clear(); - } - - /** - * @see #deletePermission(NodeRef, String, PermissionReference) - */ - public void deletePermission(PermissionEntry permissionEntry) - { - NodeRef nodeRef = permissionEntry.getNodeRef(); - String authority = permissionEntry.getAuthority(); - PermissionReference permission = permissionEntry.getPermissionReference(); - deletePermission(nodeRef, authority, permission); - } - - private void deletePermission(NodeRef nodeRef, String authority, PermissionReference perm) - { - permissionsDaoComponent.deletePermission(tenantService.getName(nodeRef), authority, perm); - accessCache.clear(); - } - - public void clearPermission(NodeRef nodeRef, String authority) - { - permissionsDaoComponent.deletePermissions(tenantService.getName(nodeRef), authority); - accessCache.clear(); - } - - private void setPermission(NodeRef nodeRef, String authority, PermissionReference perm, boolean allow) - { - permissionsDaoComponent.setPermission(tenantService.getName(nodeRef), authority, perm, allow); - accessCache.clear(); - } - - public void setPermission(PermissionEntry permissionEntry) - { - // TODO - not MT-enabled nodeRef - currently only used by tests - permissionsDaoComponent.setPermission(permissionEntry); - accessCache.clear(); - } - - public void setPermission(NodePermissionEntry nodePermissionEntry) - { - // TODO - not MT-enabled nodeRef- currently only used by tests - permissionsDaoComponent.setPermission(nodePermissionEntry); - accessCache.clear(); - } - - public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) - { - permissionsDaoComponent.setInheritParentPermissions(tenantService.getName(nodeRef), inheritParentPermissions); - accessCache.clear(); - } - - /** - * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean getInheritParentPermissions(NodeRef nodeRef) - { - return permissionsDaoComponent.getInheritParentPermissions(tenantService.getName(nodeRef)); - } - - public PermissionReference getPermissionReference(QName qname, String permissionName) - { - return modelDAO.getPermissionReference(qname, permissionName); - } - - public PermissionReference getAllPermissionReference() - { - return allPermissionReference; - } - - public String getPermission(PermissionReference permissionReference) - { - if (modelDAO.isUnique(permissionReference)) - { - return permissionReference.getName(); - } - else - { - return permissionReference.toString(); - } - } - - public PermissionReference getPermissionReference(String permissionName) - { - return modelDAO.getPermissionReference(null, permissionName); - } - - public Set getSettablePermissionReferences(QName type) - { - return modelDAO.getExposedPermissions(type); - } - - public Set getSettablePermissionReferences(NodeRef nodeRef) - { - return modelDAO.getExposedPermissions(tenantService.getName(nodeRef)); - } - - public void deletePermission(NodeRef nodeRef, String authority, String perm) - { - deletePermission(nodeRef, authority, getPermissionReference(perm)); - } - - public AccessStatus hasPermission(NodeRef nodeRef, String perm) - { - return hasPermission(nodeRef, getPermissionReference(perm)); - } - - public void setPermission(NodeRef nodeRef, String authority, String perm, boolean allow) - { - setPermission(nodeRef, authority, getPermissionReference(perm), allow); - } - - public void deletePermissions(String recipient) - { - permissionsDaoComponent.deletePermissions(recipient); - accessCache.clear(); - } - - // - // SUPPORT CLASSES - // - - /** - * Support class to test the permission on a node. - * - * @author Andy Hind - */ - private class NodeTest - { - /* - * The required permission. - */ - PermissionReference required; - - /* - * Granters of the permission - */ - Set granters; - - /* - * The additional permissions required at the node level. - */ - Set nodeRequirements = new HashSet(); - - /* - * The additional permissions required on the parent. - */ - Set parentRequirements = new HashSet(); - - /* - * The permissions required on all children . - */ - Set childrenRequirements = new HashSet(); - - /* - * The type name of the node. - */ - QName typeQName; - - /* - * The aspects set on the node. - */ - Set aspectQNames; - - /* - * Constructor just gets the additional requirements - */ - NodeTest(PermissionReference required, QName typeQName, Set aspectQNames) - { - this.required = required; - this.typeQName = typeQName; - this.aspectQNames = aspectQNames; - - // Set the required node permissions - if (required.equals(getPermissionReference(ALL_PERMISSIONS))) - { - nodeRequirements = modelDAO.getRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), typeQName, aspectQNames, RequiredPermission.On.NODE); - } - else - { - nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.NODE); - } - - parentRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.PARENT); - - childrenRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.CHILDREN); - - // Find all the permissions that grant the allowed permission - // All permissions are treated specially. - granters = new LinkedHashSet(128, 1.0f); - granters.addAll(modelDAO.getGrantingPermissions(required)); - granters.add(getAllPermissionReference()); - granters.add(OLD_ALL_PERMISSIONS_REFERENCE); - } - - /** - * External hook point - * - * @param authorisations - * @param nodeRef - * @return true if allowed - */ - boolean evaluate(Set authorisations, NodeRef nodeRef) - { - Set> denied = new HashSet>(); - return evaluate(authorisations, nodeRef, denied, null); - } - - /** - * Internal hook point for recursion - * - * @param authorisations - * @param nodeRef - * @param denied - * @param recursiveIn - * @return true if allowed - */ - boolean evaluate(Set authorisations, NodeRef nodeRef, Set> denied, MutableBoolean recursiveIn) - { - // Do we defer our required test to a parent (yes if not null) - MutableBoolean recursiveOut = null; - - Set> locallyDenied = new HashSet>(); - locallyDenied.addAll(denied); - locallyDenied.addAll(getDenied(nodeRef)); - - // Start out true and "and" all other results - boolean success = true; - - // Check the required permissions but not for sets they rely on - // their underlying permissions - if (modelDAO.checkPermission(required)) - { - if (parentRequirements.contains(required)) - { - if (checkGlobalPermissions(authorisations) || checkRequired(authorisations, nodeRef, locallyDenied)) - { - // No need to do the recursive test as it has been found - if (recursiveIn != null) - { - recursiveIn.setValue(true); - } - } - else - { - // Much cheaper to do this as we go then check all the - // stack values for each parent - recursiveOut = new MutableBoolean(false); - } - } - else - { - // We have to do the test as no parent will help us out - success &= hasSinglePermission(authorisations, nodeRef); - } - if (!success) - { - return false; - } - } - - // Check the other permissions required on the node - for (PermissionReference pr : nodeRequirements) - { - // Build a new test - NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); - success &= nt.evaluate(authorisations, nodeRef, locallyDenied, null); - if (!success) - { - return false; - } - } - - // Check the permission required of the parent - - if (success) - { - ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); - if (car.getParentRef() != null) - { - - NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); - if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) - { - - locallyDenied.addAll(getDenied(car.getParentRef())); - for (PermissionReference pr : parentRequirements) - { - if (pr.equals(required)) - { - // Recursive permission - success &= this.evaluate(authorisations, car.getParentRef(), locallyDenied, recursiveOut); - if ((recursiveOut != null) && recursiveOut.getValue()) - { - if (recursiveIn != null) - { - recursiveIn.setValue(true); - } - } - } - else - { - NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); - success &= nt.evaluate(authorisations, car.getParentRef(), locallyDenied, null); - } - - if (!success) - { - return false; - } - } - } - } - } - - if ((recursiveOut != null) && (!recursiveOut.getValue())) - { - // The required authentication was not resolved in recursion - return false; - } - - // Check permissions required of children - if (childrenRequirements.size() > 0) - { - List childAssocRefs = nodeService.getChildAssocs(nodeRef); - for (PermissionReference pr : childrenRequirements) - { - for (ChildAssociationRef child : childAssocRefs) - { - success &= (hasPermission(child.getChildRef(), pr) == AccessStatus.ALLOWED); - if (!success) - { - return false; - } - } - } - } - - return success; - } - - boolean hasSinglePermission(Set authorisations, NodeRef nodeRef) - { - nodeRef = tenantService.getName(nodeRef); - - Serializable key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION_GLOBAL); - - AccessStatus status = accessCache.get(key); - if (status != null) - { - return status == AccessStatus.ALLOWED; - } - - // Check global permission - - if (checkGlobalPermissions(authorisations)) - { - accessCache.put(key, AccessStatus.ALLOWED); - return true; - } - - Set> denied = new HashSet>(); - - return hasSinglePermission(authorisations, nodeRef, denied); - - } - - boolean hasSinglePermission(Set authorisations, NodeRef nodeRef, Set> denied) - { - nodeRef = tenantService.getName(nodeRef); - - // Add any denied permission to the denied list - these can not - // then - // be used to given authentication. - // A -> B -> C - // If B denies all permissions to any - allowing all permissions - // to - // andy at node A has no effect - - denied.addAll(getDenied(nodeRef)); - - // Cache non denied - Serializable key = null; - if (denied.size() == 0) - { - key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION); - } - if (key != null) - { - AccessStatus status = accessCache.get(key); - if (status != null) - { - return status == AccessStatus.ALLOWED; - } - } - - // If the current node allows the permission we are done - // The test includes any parent or ancestor requirements - if (checkRequired(authorisations, nodeRef, denied)) - { - if (key != null) - { - accessCache.put(key, AccessStatus.ALLOWED); - } - return true; - } - - // Permissions are only evaluated up the primary parent chain - // TODO: Do not ignore non primary permissions - ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); - - // Build the next element of the evaluation chain - if (car.getParentRef() != null) - { - NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef); - if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) - { - if (hasSinglePermission(authorisations, car.getParentRef(), denied)) - { - if (key != null) - { - accessCache.put(key, AccessStatus.ALLOWED); - } - return true; - } - else - { - if (key != null) - { - accessCache.put(key, AccessStatus.DENIED); - } - return false; - } - } - else - { - if (key != null) - { - accessCache.put(key, AccessStatus.DENIED); - } - return false; - } - } - else - { - if (key != null) - { - accessCache.put(key, AccessStatus.DENIED); - } - return false; - } - } - - /** - * Check if we have a global permission - * - * @param authorisations - * @return true if allowed - */ - private boolean checkGlobalPermissions(Set authorisations) - { - for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) - { - if (isGranted(pe, authorisations, null)) - { - return true; - } - } - return false; - } - - /** - * Get the list of permissions denied for this node. - * - * @param nodeRef - * @return the list of denied permissions - */ - Set> getDenied(NodeRef nodeRef) - { - Set> deniedSet = new HashSet>(); - - // Loop over all denied permissions - NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); - if (nodeEntry != null) - { - for (PermissionEntry pe : nodeEntry.getPermissionEntries()) - { - if (pe.isDenied()) - { - // All the sets that grant this permission must be - // denied - // Note that granters includes the orginal permission - Set granters = modelDAO.getGrantingPermissions(pe.getPermissionReference()); - for (PermissionReference granter : granters) - { - deniedSet.add(new Pair(pe.getAuthority(), granter)); - } - - // All the things granted by this permission must be - // denied - Set grantees = modelDAO.getGranteePermissions(pe.getPermissionReference()); - for (PermissionReference grantee : grantees) - { - deniedSet.add(new Pair(pe.getAuthority(), grantee)); - } - - // All permission excludes all permissions available for - // the node. - if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) - { - deniedSet.add(new Pair(pe.getAuthority(), deny)); - } - } - } - } - } - return deniedSet; - } - - /** - * Check that a given authentication is available on a node - * - * @param authorisations - * @param nodeRef - * @param denied - * @return true if the check is required - */ - boolean checkRequired(Set authorisations, NodeRef nodeRef, Set> denied) - { - NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); - - // No permissions set - short cut to deny - if (nodeEntry == null) - { - return false; - } - - // Check if each permission allows - the first wins. - // We could have other voting style mechanisms here - for (PermissionEntry pe : nodeEntry.getPermissionEntries()) - { - if (isGranted(pe, authorisations, denied)) - { - return true; - } - } - return false; - } - - /** - * Is a permission granted - * - * @param pe - - * the permissions entry to consider - * @param granters - - * the set of granters - * @param authorisations - - * the set of authorities - * @param denied - - * the set of denied permissions/authority pais - * @return true if granted - */ - private boolean isGranted(PermissionEntry pe, Set authorisations, Set> denied) - { - // If the permission entry denies then we just deny - if (pe.isDenied()) - { - return false; - } - - // The permission is allowed but we deny it as it is in the denied - // set - - if (denied != null) - { - Pair specific = new Pair(pe.getAuthority(), required); - if (denied.contains(specific)) - { - return false; - } - } - - // any deny denies - - if (false) - { - if (denied != null) - { - for (String auth : authorisations) - { - Pair specific = new Pair(auth, required); - if (denied.contains(specific)) - { - return false; - } - for (PermissionReference perm : granters) - { - specific = new Pair(auth, perm); - if (denied.contains(specific)) - { - return false; - } - } - } - } - } - - // If the permission has a match in both the authorities and - // granters list it is allowed - // It applies to the current user and it is granted - if (authorisations.contains(pe.getAuthority()) && granters.contains(pe.getPermissionReference())) - { - { - return true; - } - } - - // Default deny - return false; - } - - } - - /** - * Test a permission in the context of the new ACL implementation. All components of the ACL are in the object - - * there is no need to walk up the parent chain. Parent conditions cna not be applied as there is no context to do - * this. Child conditions can not be applied as there is no context to do this - * - * @author andyh - */ - - private class AclTest - { - /* - * The required permission. - */ - PermissionReference required; - - /* - * Granters of the permission - */ - Set granters; - - /* - * The additional permissions required at the node level. - */ - Set nodeRequirements = new HashSet(); - - /* - * The type name of the node. - */ - QName typeQName; - - /* - * The aspects set on the node. - */ - Set aspectQNames; - - /* - * Constructor just gets the additional requirements - */ - AclTest(PermissionReference required, QName typeQName, Set aspectQNames) - { - this.required = required; - this.typeQName = typeQName; - this.aspectQNames = aspectQNames; - - // Set the required node permissions - if (required.equals(getPermissionReference(ALL_PERMISSIONS))) - { - nodeRequirements = modelDAO.getRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), typeQName, aspectQNames, RequiredPermission.On.NODE); - } - else - { - nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.NODE); - } - - if (modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.PARENT).size() > 0) - { - throw new IllegalStateException("Parent permissions can not be checked for an acl"); - } - - if (modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.CHILDREN).size() > 0) - { - throw new IllegalStateException("Child permissions can not be checked for an acl"); - } - - // Find all the permissions that grant the allowed permission - // All permissions are treated specially. - granters = new LinkedHashSet(128, 1.0f); - granters.addAll(modelDAO.getGrantingPermissions(required)); - granters.add(getAllPermissionReference()); - granters.add(OLD_ALL_PERMISSIONS_REFERENCE); - } - - /** - * Internal hook point for recursion - * - * @param authorisations - * @param nodeRef - * @param denied - * @param recursiveIn - * @return true if granted - */ - boolean evaluate(Set authorisations, Long aclId, PermissionContext context) - { - // Start out true and "and" all other results - boolean success = true; - - // Check the required permissions but not for sets they rely on - // their underlying permissions - if (modelDAO.checkPermission(required)) - { - - // We have to do the test as no parent will help us out - success &= hasSinglePermission(authorisations, aclId, context); - - if (!success) - { - return false; - } - } - - // Check the other permissions required on the node - for (PermissionReference pr : nodeRequirements) - { - // Build a new test - AclTest nt = new AclTest(pr, typeQName, aspectQNames); - success &= nt.evaluate(authorisations, aclId, context); - if (!success) - { - return false; - } - } - - return success; - } - - boolean hasSinglePermission(Set authorisations, Long aclId, PermissionContext context) - { - // Check global permission - - if (checkGlobalPermissions(authorisations)) - { - return true; - } - - return checkRequired(authorisations, aclId, context); - - } - - /** - * Check if we have a global permission - * - * @param authorisations - * @return true if granted - */ - private boolean checkGlobalPermissions(Set authorisations) - { - for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) - { - if (isGranted(pe, authorisations)) - { - return true; - } - } - return false; - } - - /** - * Check that a given authentication is available on a node - * - * @param authorisations - * @param nodeRef - * @param denied - * @return true if a check is required - */ - boolean checkRequired(Set authorisations, Long aclId, PermissionContext context) - { - AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); - - if (acl == null) - { - return false; - } - - Set> denied = new HashSet>(); - - // Check if each permission allows - the first wins. - // We could have other voting style mechanisms here - for (AccessControlEntry ace : acl.getEntries()) - { - if (isGranted(ace, authorisations, denied, context)) - { - return true; - } - } - return false; - } - - /** - * Is a permission granted - * - * @param pe - - * the permissions entry to consider - * @param granters - - * the set of granters - * @param authorisations - - * the set of authorities - * @param denied - - * the set of denied permissions/authority pais - * @return true if granted - */ - private boolean isGranted(AccessControlEntry ace, Set authorisations, Set> denied, PermissionContext context) - { - // If the permission entry denies then we just deny - if (ace.getAccessStatus() == AccessStatus.DENIED) - { - denied.add(new Pair(ace.getAuthority(), ace.getPermission())); - - Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); - for (PermissionReference granter : granters) - { - denied.add(new Pair(ace.getAuthority(), granter)); - } - - // All the things granted by this permission must be - // denied - Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); - for (PermissionReference grantee : grantees) - { - denied.add(new Pair(ace.getAuthority(), grantee)); - } - - // All permission excludes all permissions available for - // the node. - if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - for (PermissionReference deny : modelDAO.getAllPermissions(context.getType(), context.getAspects())) - { - denied.add(new Pair(ace.getAuthority(), deny)); - } - } - - return false; - } - - // The permission is allowed but we deny it as it is in the denied - // set - - if (denied != null) - { - Pair specific = new Pair(ace.getAuthority(), required); - if (denied.contains(specific)) - { - return false; - } - } - - // any deny denies - - if (false) - { - if (denied != null) - { - for (String auth : authorisations) - { - Pair specific = new Pair(auth, required); - if (denied.contains(specific)) - { - return false; - } - for (PermissionReference perm : granters) - { - specific = new Pair(auth, perm); - if (denied.contains(specific)) - { - return false; - } - } - } - } - } - - // If the permission has a match in both the authorities and - // granters list it is allowed - // It applies to the current user and it is granted - if (authorisations.contains(ace.getAuthority()) && granters.contains(ace.getPermission())) - { - { - return true; - } - } - - // Default deny - return false; - } - - private boolean isGranted(PermissionEntry pe, Set authorisations) - { - // If the permission entry denies then we just deny - if (pe.isDenied()) - { - return false; - } - - // If the permission has a match in both the authorities and - // granters list it is allowed - // It applies to the current user and it is granted - if (granters.contains(pe.getPermissionReference()) && authorisations.contains(pe.getAuthority())) - { - { - return true; - } - } - - // Default deny - return false; - } - - } - - /** - * Helper class to store a pair of objects which may be null - * - * @author Andy Hind - */ - private static class Pair - { - A a; - - B b; - - Pair(A a, B b) - { - this.a = a; - this.b = b; - } - - A getA() - { - return a; - } - - B getB() - { - return b; - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - if (!(this instanceof Pair)) - { - return false; - } - Pair other = (Pair) o; - return EqualsHelper.nullSafeEquals(this.getA(), other.getA()) && EqualsHelper.nullSafeEquals(this.getB(), other.getB()); - } - - @Override - public int hashCode() - { - return (((a == null) ? 0 : a.hashCode()) * 37) + ((b == null) ? 0 : b.hashCode()); - } - - } - - private static class MutableBoolean - { - private boolean value; - - MutableBoolean(boolean value) - { - this.value = value; - } - - void setValue(boolean value) - { - this.value = value; - } - - boolean getValue() - { - return value; - } - } - - public Map> getAllSetPermissionsForCurrentUser() - { - String currentUser = AuthenticationUtil.getRunAsUser(); - return getAllSetPermissionsForAuthority(currentUser); - } - - public Map> getAllSetPermissionsForAuthority(String authority) - { - return permissionsDaoComponent.getAllSetPermissions(authority); - } - - public Set findNodesByAssignedPermissionForCurrentUser(String permission, boolean allow, boolean includeContainingAuthorities, boolean exactPermissionMatch) - { - String currentUser = AuthenticationUtil.getRunAsUser(); - return findNodesByAssignedPermission(currentUser, permission, allow, includeContainingAuthorities, exactPermissionMatch); - } - - public Set findNodesByAssignedPermission(String authority, String permission, boolean allow, boolean includeContainingAuthorities, boolean includeContainingPermissions) - { - // TODO: owned nodes and add owner rights ?? - // Does not include dynamic permissions (they would have to be done by query - e.g. owership and OWNER rights) - // Does not include ACEGI auth object authorities - Set authorities = new HashSet(); - authorities.add(authority); - if (includeContainingAuthorities) - { - authorities.addAll(authorityService.getAuthoritiesForUser(authority)); - } - - HashSet answer = new HashSet(); - - PermissionReference pr = getPermissionReference(permission); - Set permissions = new HashSet(); - permissions.add(pr); - - if (includeContainingPermissions) - { - permissions.addAll(modelDAO.getGrantingPermissions(pr)); - } - - for (PermissionReference perm : permissions) - { - for (String auth : authorities) - { - answer.addAll(permissionsDaoComponent.findNodeByPermission(auth, perm, allow)); - } - } - return answer; - } - - /** - * This methods checks weather the specified NodeRef instance is an VersionedNodeRef - * - * @param nodeRef - probably VersionedNodeRef - * @return true if NodeRef if Versioned and false in other case - */ - private boolean isVersionedNodeRefInstance(NodeRef nodeRef) - { - return nodeRef.getStoreRef().getProtocol().equals(VersionModel.STORE_PROTOCOL); - } - - /** - * Converts specified VersionedNodeRef to Frozen NodeRef (from SpacesStore store, accessed by workspace protocol) - * - * @param nodeRef - always VersionedNodeRef - * @return Frozen NodeRef instance (source for this VersionedNodeRef instance) - */ - private NodeRef convertVersionedNodeRefToFrozenNodeRef(NodeRef nodeRef) - { - - Map properties = nodeService.getProperties(nodeRef); - - return new NodeRef((String) properties.get(ContentModel.PROP_STORE_PROTOCOL), - (String) properties.get(ContentModel.PROP_STORE_IDENTIFIER), - (String) properties.get(ContentModel.PROP_NODE_UUID)); - } -} +/* + * 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.repo.security.permissions.impl; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.dao.User; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.avm.AVMRepository; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.ACLType; +import org.alfresco.repo.security.permissions.AccessControlEntry; +import org.alfresco.repo.security.permissions.AccessControlList; +import org.alfresco.repo.security.permissions.AccessControlListProperties; +import org.alfresco.repo.security.permissions.DynamicAuthority; +import org.alfresco.repo.security.permissions.NodePermissionEntry; +import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.PermissionContext; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * The Alfresco implementation of a permissions service against our APIs for the permissions model and permissions + * persistence. + * + * @author andyh + */ +public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean +{ + + static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference(QName.createQName("", PermissionService.ALL_PERMISSIONS), + PermissionService.ALL_PERMISSIONS); + + private static Log log = LogFactory.getLog(PermissionServiceImpl.class); + + /** a transactionally-safe cache to be injected */ + private SimpleCache accessCache; + + /* + * Access to the model + */ + private ModelDAO modelDAO; + + /* + * Access to permissions + */ + private PermissionsDaoComponent permissionsDaoComponent; + + /* + * Access to the node service + */ + private NodeService nodeService; + + /* + * Access to the tenant service + */ + private TenantService tenantService; + + /* + * Access to the data dictionary + */ + private DictionaryService dictionaryService; + + /* + * Access to the authority component + */ + private AuthorityService authorityService; + + /* + * Dynamic authorities providers + */ + private List dynamicAuthorities; + + private PolicyComponent policyComponent; + + private AclDaoComponent aclDaoComponent; + + private PermissionReference allPermissionReference; + + /** + * Standard spring construction. + */ + public PermissionServiceImpl() + { + super(); + } + + // + // Inversion of control + // + + /** + * Set the dictionary service + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the permissions model dao + * + * @param modelDAO + */ + public void setModelDAO(ModelDAO modelDAO) + { + this.modelDAO = modelDAO; + } + + /** + * Set the node service. + * + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the tenant service. + * @param tenantService + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Set the permissions dao component + * + * @param permissionsDaoComponent + */ + public void setPermissionsDaoComponent(PermissionsDaoComponent permissionsDaoComponent) + { + this.permissionsDaoComponent = permissionsDaoComponent; + } + + /** + * Set the authority service. + * + * @param authorityService + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Set the dynamic authorities + * + * @param dynamicAuthorities + */ + public void setDynamicAuthorities(List dynamicAuthorities) + { + this.dynamicAuthorities = dynamicAuthorities; + } + + /** + * Set the ACL DAO component. + * + * @param aclDaoComponent + */ + public void setAclDaoComponent(AclDaoComponent aclDaoComponent) + { + this.aclDaoComponent = aclDaoComponent; + } + + /** + * Set the permissions access cache. + * + * @param accessCache + * a transactionally safe cache + */ + public void setAccessCache(SimpleCache accessCache) + { + this.accessCache = accessCache; + } + + /** + * Set the policy component + * + * @param policyComponent + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Cache clear on move node + * + * @param oldChildAssocRef + * @param newChildAssocRef + */ + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + accessCache.clear(); + } + + public void afterPropertiesSet() throws Exception + { + if (dictionaryService == null) + { + throw new IllegalArgumentException("Property 'dictionaryService' has not been set"); + } + if (modelDAO == null) + { + throw new IllegalArgumentException("Property 'modelDAO' has not been set"); + } + if (nodeService == null) + { + throw new IllegalArgumentException("Property 'nodeService' has not been set"); + } + if (permissionsDaoComponent == null) + { + throw new IllegalArgumentException("Property 'permissionsDAO' has not been set"); + } + if (authorityService == null) + { + throw new IllegalArgumentException("Property 'authorityService' has not been set"); + } + if (accessCache == null) + { + throw new IllegalArgumentException("Property 'accessCache' has not been set"); + } + if (policyComponent == null) + { + throw new IllegalArgumentException("Property 'policyComponent' has not been set"); + } + if (aclDaoComponent == null) + { + throw new IllegalArgumentException("Property 'aclDaoComponent' has not been set"); + } + + policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); + + allPermissionReference = getPermissionReference(ALL_PERMISSIONS); + + } + + // + // Permissions Service + // + + public String getOwnerAuthority() + { + return OWNER_AUTHORITY; + } + + public String getAllAuthorities() + { + return ALL_AUTHORITIES; + } + + public String getAllPermission() + { + return ALL_PERMISSIONS; + } + + public Set getPermissions(NodeRef nodeRef) + { + return getAllPermissionsImpl(nodeRef, true, true); + } + + public Set getAllSetPermissions(NodeRef nodeRef) + { + HashSet accessPermissions = new HashSet(); + NodePermissionEntry nodePremissionEntry = getSetPermissions(nodeRef); + for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe.getAccessStatus(), pe.getAuthority(), pe.getPosition())); + } + return accessPermissions; + } + + public Set getAllSetPermissions(StoreRef storeRef) + { + HashSet accessPermissions = new HashSet(); + NodePermissionEntry nodePremissionEntry = getSetPermissions(storeRef); + for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe.getAccessStatus(), pe.getAuthority(), pe.getPosition())); + } + return accessPermissions; + } + + private Set getAllPermissionsImpl(NodeRef nodeRef, boolean includeTrue, boolean includeFalse) + { + String userName = AuthenticationUtil.getRunAsUser(); + HashSet accessPermissions = new HashSet(); + for (PermissionReference pr : getSettablePermissionReferences(nodeRef)) + { + if (hasPermission(nodeRef, pr) == AccessStatus.ALLOWED) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.ALLOWED, userName, -1)); + } + else + { + if (includeFalse) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.DENIED, userName, -1)); + } + } + } + return accessPermissions; + } + + public Set getSettablePermissions(NodeRef nodeRef) + { + Set settable = getSettablePermissionReferences(nodeRef); + Set strings = new HashSet(settable.size()); + for (PermissionReference pr : settable) + { + strings.add(getPermission(pr)); + } + return strings; + } + + public Set getSettablePermissions(QName type) + { + Set settable = getSettablePermissionReferences(type); + Set strings = new LinkedHashSet(settable.size()); + for (PermissionReference pr : settable) + { + strings.add(getPermission(pr)); + } + return strings; + } + + public NodePermissionEntry getSetPermissions(NodeRef nodeRef) + { + return permissionsDaoComponent.getPermissions(tenantService.getName(nodeRef)); + } + + public NodePermissionEntry getSetPermissions(StoreRef storeRef) + { + return permissionsDaoComponent.getPermissions(storeRef); + } + + public AccessStatus hasPermission(NodeRef passedNodeRef, final PermissionReference permIn) + { + // If the node ref is null there is no sensible test to do - and there + // must be no permissions + // - so we allow it + if (passedNodeRef == null) + { + return AccessStatus.ALLOWED; + } + + // If the permission is null we deny + if (permIn == null) + { + return AccessStatus.DENIED; + } + + // AVM nodes - test for existence underneath + if (passedNodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) + { + return doAvmCan(passedNodeRef, permIn); + } + + // Allow permissions for nodes that do not exist + if (!nodeService.exists(passedNodeRef)) + { + return AccessStatus.ALLOWED; + } + + // Because of VersionedNodeRef has no any inherited from source Frozen NodeRef permissions (it has only default permissions), + // it is necessary to avoid cases when some user without appropriate permissions trying to receive any resource from its any version link etc. + // That could be proceed through receiving Frozen NodeRef instance for this VersionedNodeRef instance. There is appears a possibility to get + // access to specified for Frozen NodeRef instance permissions + + // NOTE: maybe in future there will appear situation when changing Node permissions will be a cause for creating new Node version. In other words, + // VersionedNodeRefs will contain their own permissions (whose, probably, will differ from version to version). In this case you should delete/comment this code!!! + if (isVersionedNodeRefInstance(passedNodeRef)) + { + passedNodeRef = convertVersionedNodeRefToFrozenNodeRef(VersionUtil.convertNodeRef(passedNodeRef)); + } + + final NodeRef nodeRef = tenantService.getName(passedNodeRef); + + final PermissionReference perm; + if (permIn.equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + perm = getAllPermissionReference(); + } + else + { + perm = permIn; + } + + if (AuthenticationUtil.getRunAsUser() == null) + { + return AccessStatus.DENIED; + } + + if (AuthenticationUtil.isRunAsUserTheSystemUser()) + { + return AccessStatus.ALLOWED; + } + + // New ACLs + + AccessControlListProperties properties = permissionsDaoComponent.getAccessControlListProperties(nodeRef); + if ((properties != null) && (properties.getAclType() != null) && (properties.getAclType() != ACLType.OLD)) + { + QName typeQname = nodeService.getType(nodeRef); + Set aspectQNames = nodeService.getAspects(nodeRef); + PermissionContext context = new PermissionContext(typeQname); + context.getAspects().addAll(aspectQNames); + Authentication auth = AuthenticationUtil.getRunAsAuthentication(); + String user = AuthenticationUtil.getRunAsUser(); + for (String dynamicAuthority : getDynamicAuthorities(auth, nodeRef, perm)) + { + context.addDynamicAuthorityAssignment(user, dynamicAuthority); + } + return hasPermission(properties.getId(), context, perm); + } + + // Get the current authentications + // Use the smart authentication cache to improve permissions performance + Authentication auth = AuthenticationUtil.getRunAsAuthentication(); + final Set authorisations = getAuthorisations(auth, nodeRef, perm); + + // If the node does not support the given permission there is no point + // doing the test + Set available = AuthenticationUtil.runAs(new RunAsWork>() + { + public Set doWork() throws Exception + { + return modelDAO.getAllPermissions(nodeRef); + } + + }, AuthenticationUtil.getSystemUserName()); + + available.add(getAllPermissionReference()); + available.add(OLD_ALL_PERMISSIONS_REFERENCE); + + final Serializable key = generateKey(authorisations, nodeRef, perm, CacheType.HAS_PERMISSION); + if (!(available.contains(perm))) + { + accessCache.put(key, AccessStatus.DENIED); + return AccessStatus.DENIED; + } + + if (AuthenticationUtil.isRunAsUserTheSystemUser()) + { + return AccessStatus.ALLOWED; + } + + return AuthenticationUtil.runAs(new RunAsWork() + { + + public AccessStatus doWork() throws Exception + { + + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status; + } + + // + // TODO: Dynamic permissions via evaluators + // + + /* + * Does the current authentication have the supplied permission on the given node. + */ + + QName typeQname = nodeService.getType(nodeRef); + Set aspectQNames = nodeService.getAspects(nodeRef); + + NodeTest nt = new NodeTest(perm, typeQname, aspectQNames); + boolean result = nt.evaluate(authorisations, nodeRef); + if (log.isDebugEnabled()) + { + log.debug("Permission <" + + perm + "> is " + (result ? "allowed" : "denied") + " for " + AuthenticationUtil.getRunAsUser() + " on node " + + nodeService.getPath(nodeRef)); + } + + status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; + accessCache.put(key, status); + return status; + } + }, AuthenticationUtil.getSystemUserName()); + + } + + private AccessStatus doAvmCan(NodeRef nodeRef, PermissionReference permission) + { + org.alfresco.util.Pair avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); + int version = avmVersionPath.getFirst(); + String path = avmVersionPath.getSecond(); + boolean result = AVMRepository.GetInstance().can(nodeRef.getStoreRef().getIdentifier(), version, path, permission.getName()); + AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; + return status; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.security.PermissionService#hasPermission(java.lang.Long, java.lang.String, + * java.lang.String) + */ + public AccessStatus hasPermission(Long aclID, PermissionContext context, String permission) + { + return hasPermission(aclID, context, getPermissionReference(permission)); + } + + + private AccessStatus hasPermission(Long aclId, PermissionContext context, PermissionReference permission) + { + if (aclId == null) + { + // Enforce store ACLs if set - the AVM default was to "allow" if there are no permissions set ... + if (context.getStoreAcl() == null) + { + return AccessStatus.ALLOWED; + } + else + { + if (AuthenticationUtil.isRunAsUserTheSystemUser()) + { + return AccessStatus.ALLOWED; + } + + Authentication auth = AuthenticationUtil.getRunAsAuthentication(); + if (auth == null) + { + throw new IllegalStateException("Unauthenticated"); + } + Set storeAuthorisations = getAuthorisations(auth, (PermissionContext) null); + QName typeQname = context.getType(); + Set aspectQNames = context.getAspects(); + AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); + boolean result = aclTest.evaluate(storeAuthorisations, context.getStoreAcl(), context); + AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; + return status; + } + } + + if (permission == null) + { + return AccessStatus.DENIED; + } + + if (AuthenticationUtil.getRunAsUser() == null) + { + return AccessStatus.DENIED; + } + + if (AuthenticationUtil.getRunAsUser().equals(AuthenticationUtil.getSystemUserName())) + { + return AccessStatus.ALLOWED; + } + + // Get the current authentications + // Use the smart authentication cache to improve permissions performance + Authentication auth = AuthenticationUtil.getRunAsAuthentication(); + if (auth == null) + { + throw new IllegalStateException("Unauthenticated"); + } + + Set authorisations = getAuthorisations(auth, context); + + // If the node does not support the given permission there is no point + // doing the test + + final QName typeQname = context.getType(); + final Set aspectQNames = context.getAspects(); + + Set available = AuthenticationUtil.runAs(new RunAsWork>() + { + public Set doWork() throws Exception + { + return modelDAO.getAllPermissions(typeQname, aspectQNames); + } + + }, AuthenticationUtil.getSystemUserName()); + available.add(getAllPermissionReference()); + available.add(OLD_ALL_PERMISSIONS_REFERENCE); + + if (!(available.contains(permission))) + { + return AccessStatus.DENIED; + } + + if (AuthenticationUtil.isRunAsUserTheSystemUser()) + { + return AccessStatus.ALLOWED; + } + + if (permission.equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + permission = getAllPermissionReference(); + } + + boolean result; + if (context.getStoreAcl() == null) + { + AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); + result = aclTest.evaluate(authorisations, aclId, context); + } + else + { + Set storeAuthorisations = getAuthorisations(auth, (PermissionContext) null); + AclTest aclTest = new AclTest(permission, typeQname, aspectQNames); + result = aclTest.evaluate(authorisations, aclId, context) && aclTest.evaluate(storeAuthorisations, context.getStoreAcl(), context); + } + AccessStatus status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; + return status; + + } + + /** + * Control permissions cache - only used when we do old style permission evaluations + * - which should only be in DM stores where no permissions have been set + * + * @author andyh + * + */ + enum CacheType + { + /** + * cache full check + */ + HAS_PERMISSION, + /** + * Cache single permission check + */ + SINGLE_PERMISSION, + /** + * Cache single permission check for global permission checks + */ + SINGLE_PERMISSION_GLOBAL; + } + + /** + * Key for a cache object is built from all the known Authorities (which can change dynamically so they must all be + * used) the NodeRef ID and the permission reference itself. This gives a unique key for each permission test. + */ + static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm, CacheType type) + { + LinkedHashSet key = new LinkedHashSet(); + key.add(perm.toString()); + key.addAll(auths); + key.add(nodeRef); + key.add(type); + return key; + } + + /** + * Get the authorisations for the currently authenticated user + * + * @param auth + * @return the set of authorisations + */ + private Set getAuthorisations(Authentication auth, NodeRef nodeRef, PermissionReference required) + { + + HashSet auths = new HashSet(); + // No authenticated user then no permissions + if (auth == null) + { + return auths; + } + // TODO: Refactor and use the authentication service for this. + User user = (User) auth.getPrincipal(); + + String username = user.getUsername(); + auths.add(username); + + if (tenantService.getBaseNameUser(username).equalsIgnoreCase(PermissionService.GUEST_AUTHORITY)) + { + auths.add(PermissionService.GUEST_AUTHORITY); + } + + for (GrantedAuthority authority : auth.getAuthorities()) + { + auths.add(authority.getAuthority()); + } + auths.addAll(getDynamicAuthorities(auth, nodeRef, required)); + auths.addAll(authorityService.getAuthoritiesForUser(username)); + return auths; + } + + private Set getDynamicAuthorities(Authentication auth, NodeRef nodeRef, PermissionReference required) + { + HashSet auths = new HashSet(); + + if (auth == null) + { + return auths; + } + User user = (User) auth.getPrincipal(); + String username = user.getUsername(); + + nodeRef = tenantService.getName(nodeRef); + if (nodeRef != null) + { + if (dynamicAuthorities != null) + { + for (DynamicAuthority da : dynamicAuthorities) + { + Set requiredFor = da.requiredFor(); + if ((requiredFor == null) || (requiredFor.contains(required))) + { + if (da.hasAuthority(nodeRef, username)) + { + auths.add(da.getAuthority()); + } + } + } + } + } + auths.addAll(authorityService.getAuthoritiesForUser(user.getUsername())); + return auths; + } + + private Set getAuthorisations(Authentication auth, PermissionContext context) + { + HashSet auths = new HashSet(); + // No authenticated user then no permissions + if (auth == null) + { + return auths; + } + // TODO: Refactor and use the authentication service for this. + User user = (User) auth.getPrincipal(); + auths.add(user.getUsername()); + for (GrantedAuthority authority : auth.getAuthorities()) + { + auths.add(authority.getAuthority()); + } + auths.addAll(authorityService.getAuthoritiesForUser(user.getUsername())); + + if (context != null) + { + Map> dynamicAuthorityAssignments = context.getDynamicAuthorityAssignment(); + HashSet dynAuths = new HashSet(); + for (String current : auths) + { + Set dynos = dynamicAuthorityAssignments.get(current); + if (dynos != null) + { + dynAuths.addAll(dynos); + } + } + auths.addAll(dynAuths); + } + + return auths; + } + + public NodePermissionEntry explainPermission(NodeRef nodeRef, PermissionReference perm) + { + // TODO Auto-generated method stub + return null; + } + + public void clearPermission(StoreRef storeRef, String authority) + { + permissionsDaoComponent.deletePermissions(storeRef, authority); + accessCache.clear(); + + } + + public void deletePermission(StoreRef storeRef, String authority, String perm) + { + deletePermission(storeRef, authority, getPermissionReference(perm)); + } + + + private void deletePermission(StoreRef storeRef, String authority, PermissionReference perm) + { + permissionsDaoComponent.deletePermission(storeRef, authority, perm); + accessCache.clear(); + } + + public void deletePermissions(StoreRef storeRef) + { + permissionsDaoComponent.deletePermissions(storeRef); + accessCache.clear(); + + } + + public void setPermission(StoreRef storeRef, String authority, String perm, boolean allow) + { + setPermission(storeRef, authority, getPermissionReference(perm), allow); + } + + private void setPermission(StoreRef storeRef, String authority, PermissionReference permission, boolean allow) + { + permissionsDaoComponent.setPermission(storeRef, authority, permission, allow); + accessCache.clear(); + + } + + public void deletePermissions(NodeRef nodeRef) + { + permissionsDaoComponent.deletePermissions(tenantService.getName(nodeRef)); + accessCache.clear(); + } + + public void deletePermissions(NodePermissionEntry nodePermissionEntry) + { + permissionsDaoComponent.deletePermissions(tenantService.getName(nodePermissionEntry.getNodeRef())); + accessCache.clear(); + } + + /** + * @see #deletePermission(NodeRef, String, PermissionReference) + */ + public void deletePermission(PermissionEntry permissionEntry) + { + NodeRef nodeRef = permissionEntry.getNodeRef(); + String authority = permissionEntry.getAuthority(); + PermissionReference permission = permissionEntry.getPermissionReference(); + deletePermission(nodeRef, authority, permission); + } + + private void deletePermission(NodeRef nodeRef, String authority, PermissionReference perm) + { + permissionsDaoComponent.deletePermission(tenantService.getName(nodeRef), authority, perm); + accessCache.clear(); + } + + public void clearPermission(NodeRef nodeRef, String authority) + { + permissionsDaoComponent.deletePermissions(tenantService.getName(nodeRef), authority); + accessCache.clear(); + } + + private void setPermission(NodeRef nodeRef, String authority, PermissionReference perm, boolean allow) + { + permissionsDaoComponent.setPermission(tenantService.getName(nodeRef), authority, perm, allow); + accessCache.clear(); + } + + public void setPermission(PermissionEntry permissionEntry) + { + // TODO - not MT-enabled nodeRef - currently only used by tests + permissionsDaoComponent.setPermission(permissionEntry); + accessCache.clear(); + } + + public void setPermission(NodePermissionEntry nodePermissionEntry) + { + // TODO - not MT-enabled nodeRef- currently only used by tests + permissionsDaoComponent.setPermission(nodePermissionEntry); + accessCache.clear(); + } + + public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) + { + permissionsDaoComponent.setInheritParentPermissions(tenantService.getName(nodeRef), inheritParentPermissions); + accessCache.clear(); + } + + /** + * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean getInheritParentPermissions(NodeRef nodeRef) + { + return permissionsDaoComponent.getInheritParentPermissions(tenantService.getName(nodeRef)); + } + + public PermissionReference getPermissionReference(QName qname, String permissionName) + { + return modelDAO.getPermissionReference(qname, permissionName); + } + + public PermissionReference getAllPermissionReference() + { + return allPermissionReference; + } + + public String getPermission(PermissionReference permissionReference) + { + if (modelDAO.isUnique(permissionReference)) + { + return permissionReference.getName(); + } + else + { + return permissionReference.toString(); + } + } + + public PermissionReference getPermissionReference(String permissionName) + { + return modelDAO.getPermissionReference(null, permissionName); + } + + public Set getSettablePermissionReferences(QName type) + { + return modelDAO.getExposedPermissions(type); + } + + public Set getSettablePermissionReferences(NodeRef nodeRef) + { + return modelDAO.getExposedPermissions(tenantService.getName(nodeRef)); + } + + public void deletePermission(NodeRef nodeRef, String authority, String perm) + { + deletePermission(nodeRef, authority, getPermissionReference(perm)); + } + + public AccessStatus hasPermission(NodeRef nodeRef, String perm) + { + return hasPermission(nodeRef, getPermissionReference(perm)); + } + + public void setPermission(NodeRef nodeRef, String authority, String perm, boolean allow) + { + setPermission(nodeRef, authority, getPermissionReference(perm), allow); + } + + public void deletePermissions(String recipient) + { + permissionsDaoComponent.deletePermissions(recipient); + accessCache.clear(); + } + + // + // SUPPORT CLASSES + // + + /** + * Support class to test the permission on a node. + * + * @author Andy Hind + */ + private class NodeTest + { + /* + * The required permission. + */ + PermissionReference required; + + /* + * Granters of the permission + */ + Set granters; + + /* + * The additional permissions required at the node level. + */ + Set nodeRequirements = new HashSet(); + + /* + * The additional permissions required on the parent. + */ + Set parentRequirements = new HashSet(); + + /* + * The permissions required on all children . + */ + Set childrenRequirements = new HashSet(); + + /* + * The type name of the node. + */ + QName typeQName; + + /* + * The aspects set on the node. + */ + Set aspectQNames; + + /* + * Constructor just gets the additional requirements + */ + NodeTest(PermissionReference required, QName typeQName, Set aspectQNames) + { + this.required = required; + this.typeQName = typeQName; + this.aspectQNames = aspectQNames; + + // Set the required node permissions + if (required.equals(getPermissionReference(ALL_PERMISSIONS))) + { + nodeRequirements = modelDAO.getRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), typeQName, aspectQNames, RequiredPermission.On.NODE); + } + else + { + nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.NODE); + } + + parentRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.PARENT); + + childrenRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.CHILDREN); + + // Find all the permissions that grant the allowed permission + // All permissions are treated specially. + granters = new LinkedHashSet(128, 1.0f); + granters.addAll(modelDAO.getGrantingPermissions(required)); + granters.add(getAllPermissionReference()); + granters.add(OLD_ALL_PERMISSIONS_REFERENCE); + } + + /** + * External hook point + * + * @param authorisations + * @param nodeRef + * @return true if allowed + */ + boolean evaluate(Set authorisations, NodeRef nodeRef) + { + Set> denied = new HashSet>(); + return evaluate(authorisations, nodeRef, denied, null); + } + + /** + * Internal hook point for recursion + * + * @param authorisations + * @param nodeRef + * @param denied + * @param recursiveIn + * @return true if allowed + */ + boolean evaluate(Set authorisations, NodeRef nodeRef, Set> denied, MutableBoolean recursiveIn) + { + // Do we defer our required test to a parent (yes if not null) + MutableBoolean recursiveOut = null; + + Set> locallyDenied = new HashSet>(); + locallyDenied.addAll(denied); + locallyDenied.addAll(getDenied(nodeRef)); + + // Start out true and "and" all other results + boolean success = true; + + // Check the required permissions but not for sets they rely on + // their underlying permissions + if (modelDAO.checkPermission(required)) + { + if (parentRequirements.contains(required)) + { + if (checkGlobalPermissions(authorisations) || checkRequired(authorisations, nodeRef, locallyDenied)) + { + // No need to do the recursive test as it has been found + if (recursiveIn != null) + { + recursiveIn.setValue(true); + } + } + else + { + // Much cheaper to do this as we go then check all the + // stack values for each parent + recursiveOut = new MutableBoolean(false); + } + } + else + { + // We have to do the test as no parent will help us out + success &= hasSinglePermission(authorisations, nodeRef); + } + if (!success) + { + return false; + } + } + + // Check the other permissions required on the node + for (PermissionReference pr : nodeRequirements) + { + // Build a new test + NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); + success &= nt.evaluate(authorisations, nodeRef, locallyDenied, null); + if (!success) + { + return false; + } + } + + // Check the permission required of the parent + + if (success) + { + ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); + if (car.getParentRef() != null) + { + + NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); + if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) + { + + locallyDenied.addAll(getDenied(car.getParentRef())); + for (PermissionReference pr : parentRequirements) + { + if (pr.equals(required)) + { + // Recursive permission + success &= this.evaluate(authorisations, car.getParentRef(), locallyDenied, recursiveOut); + if ((recursiveOut != null) && recursiveOut.getValue()) + { + if (recursiveIn != null) + { + recursiveIn.setValue(true); + } + } + } + else + { + NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); + success &= nt.evaluate(authorisations, car.getParentRef(), locallyDenied, null); + } + + if (!success) + { + return false; + } + } + } + } + } + + if ((recursiveOut != null) && (!recursiveOut.getValue())) + { + // The required authentication was not resolved in recursion + return false; + } + + // Check permissions required of children + if (childrenRequirements.size() > 0) + { + List childAssocRefs = nodeService.getChildAssocs(nodeRef); + for (PermissionReference pr : childrenRequirements) + { + for (ChildAssociationRef child : childAssocRefs) + { + success &= (hasPermission(child.getChildRef(), pr) == AccessStatus.ALLOWED); + if (!success) + { + return false; + } + } + } + } + + return success; + } + + boolean hasSinglePermission(Set authorisations, NodeRef nodeRef) + { + nodeRef = tenantService.getName(nodeRef); + + Serializable key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION_GLOBAL); + + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status == AccessStatus.ALLOWED; + } + + // Check global permission + + if (checkGlobalPermissions(authorisations)) + { + accessCache.put(key, AccessStatus.ALLOWED); + return true; + } + + Set> denied = new HashSet>(); + + return hasSinglePermission(authorisations, nodeRef, denied); + + } + + boolean hasSinglePermission(Set authorisations, NodeRef nodeRef, Set> denied) + { + nodeRef = tenantService.getName(nodeRef); + + // Add any denied permission to the denied list - these can not + // then + // be used to given authentication. + // A -> B -> C + // If B denies all permissions to any - allowing all permissions + // to + // andy at node A has no effect + + denied.addAll(getDenied(nodeRef)); + + // Cache non denied + Serializable key = null; + if (denied.size() == 0) + { + key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION); + } + if (key != null) + { + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status == AccessStatus.ALLOWED; + } + } + + // If the current node allows the permission we are done + // The test includes any parent or ancestor requirements + if (checkRequired(authorisations, nodeRef, denied)) + { + if (key != null) + { + accessCache.put(key, AccessStatus.ALLOWED); + } + return true; + } + + // Permissions are only evaluated up the primary parent chain + // TODO: Do not ignore non primary permissions + ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); + + // Build the next element of the evaluation chain + if (car.getParentRef() != null) + { + NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef); + if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) + { + if (hasSinglePermission(authorisations, car.getParentRef(), denied)) + { + if (key != null) + { + accessCache.put(key, AccessStatus.ALLOWED); + } + return true; + } + else + { + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; + } + } + else + { + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; + } + } + else + { + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; + } + } + + /** + * Check if we have a global permission + * + * @param authorisations + * @return true if allowed + */ + private boolean checkGlobalPermissions(Set authorisations) + { + for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) + { + if (isGranted(pe, authorisations, null)) + { + return true; + } + } + return false; + } + + /** + * Get the list of permissions denied for this node. + * + * @param nodeRef + * @return the list of denied permissions + */ + Set> getDenied(NodeRef nodeRef) + { + Set> deniedSet = new HashSet>(); + + // Loop over all denied permissions + NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); + if (nodeEntry != null) + { + for (PermissionEntry pe : nodeEntry.getPermissionEntries()) + { + if (pe.isDenied()) + { + // All the sets that grant this permission must be + // denied + // Note that granters includes the orginal permission + Set granters = modelDAO.getGrantingPermissions(pe.getPermissionReference()); + for (PermissionReference granter : granters) + { + deniedSet.add(new Pair(pe.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(pe.getPermissionReference()); + for (PermissionReference grantee : grantees) + { + deniedSet.add(new Pair(pe.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) + { + deniedSet.add(new Pair(pe.getAuthority(), deny)); + } + } + } + } + } + return deniedSet; + } + + /** + * Check that a given authentication is available on a node + * + * @param authorisations + * @param nodeRef + * @param denied + * @return true if the check is required + */ + boolean checkRequired(Set authorisations, NodeRef nodeRef, Set> denied) + { + NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); + + // No permissions set - short cut to deny + if (nodeEntry == null) + { + return false; + } + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (PermissionEntry pe : nodeEntry.getPermissionEntries()) + { + if (isGranted(pe, authorisations, denied)) + { + return true; + } + } + return false; + } + + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isGranted(PermissionEntry pe, Set authorisations, Set> denied) + { + // If the permission entry denies then we just deny + if (pe.isDenied()) + { + return false; + } + + // The permission is allowed but we deny it as it is in the denied + // set + + if (denied != null) + { + Pair specific = new Pair(pe.getAuthority(), required); + if (denied.contains(specific)) + { + return false; + } + } + + // any deny denies + + if (false) + { + if (denied != null) + { + for (String auth : authorisations) + { + Pair specific = new Pair(auth, required); + if (denied.contains(specific)) + { + return false; + } + for (PermissionReference perm : granters) + { + specific = new Pair(auth, perm); + if (denied.contains(specific)) + { + return false; + } + } + } + } + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authorisations.contains(pe.getAuthority()) && granters.contains(pe.getPermissionReference())) + { + { + return true; + } + } + + // Default deny + return false; + } + + } + + /** + * Test a permission in the context of the new ACL implementation. All components of the ACL are in the object - + * there is no need to walk up the parent chain. Parent conditions cna not be applied as there is no context to do + * this. Child conditions can not be applied as there is no context to do this + * + * @author andyh + */ + + private class AclTest + { + /* + * The required permission. + */ + PermissionReference required; + + /* + * Granters of the permission + */ + Set granters; + + /* + * The additional permissions required at the node level. + */ + Set nodeRequirements = new HashSet(); + + /* + * The type name of the node. + */ + QName typeQName; + + /* + * The aspects set on the node. + */ + Set aspectQNames; + + /* + * Constructor just gets the additional requirements + */ + AclTest(PermissionReference required, QName typeQName, Set aspectQNames) + { + this.required = required; + this.typeQName = typeQName; + this.aspectQNames = aspectQNames; + + // Set the required node permissions + if (required.equals(getPermissionReference(ALL_PERMISSIONS))) + { + nodeRequirements = modelDAO.getRequiredPermissions(getPermissionReference(PermissionService.FULL_CONTROL), typeQName, aspectQNames, RequiredPermission.On.NODE); + } + else + { + nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.NODE); + } + + if (modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.PARENT).size() > 0) + { + throw new IllegalStateException("Parent permissions can not be checked for an acl"); + } + + if (modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, RequiredPermission.On.CHILDREN).size() > 0) + { + throw new IllegalStateException("Child permissions can not be checked for an acl"); + } + + // Find all the permissions that grant the allowed permission + // All permissions are treated specially. + granters = new LinkedHashSet(128, 1.0f); + granters.addAll(modelDAO.getGrantingPermissions(required)); + granters.add(getAllPermissionReference()); + granters.add(OLD_ALL_PERMISSIONS_REFERENCE); + } + + /** + * Internal hook point for recursion + * + * @param authorisations + * @param nodeRef + * @param denied + * @param recursiveIn + * @return true if granted + */ + boolean evaluate(Set authorisations, Long aclId, PermissionContext context) + { + // Start out true and "and" all other results + boolean success = true; + + // Check the required permissions but not for sets they rely on + // their underlying permissions + if (modelDAO.checkPermission(required)) + { + + // We have to do the test as no parent will help us out + success &= hasSinglePermission(authorisations, aclId, context); + + if (!success) + { + return false; + } + } + + // Check the other permissions required on the node + for (PermissionReference pr : nodeRequirements) + { + // Build a new test + AclTest nt = new AclTest(pr, typeQName, aspectQNames); + success &= nt.evaluate(authorisations, aclId, context); + if (!success) + { + return false; + } + } + + return success; + } + + boolean hasSinglePermission(Set authorisations, Long aclId, PermissionContext context) + { + // Check global permission + + if (checkGlobalPermissions(authorisations)) + { + return true; + } + + return checkRequired(authorisations, aclId, context); + + } + + /** + * Check if we have a global permission + * + * @param authorisations + * @return true if granted + */ + private boolean checkGlobalPermissions(Set authorisations) + { + for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) + { + if (isGranted(pe, authorisations)) + { + return true; + } + } + return false; + } + + /** + * Check that a given authentication is available on a node + * + * @param authorisations + * @param nodeRef + * @param denied + * @return true if a check is required + */ + boolean checkRequired(Set authorisations, Long aclId, PermissionContext context) + { + AccessControlList acl = aclDaoComponent.getAccessControlList(aclId); + + if (acl == null) + { + return false; + } + + Set> denied = new HashSet>(); + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (AccessControlEntry ace : acl.getEntries()) + { + if (isGranted(ace, authorisations, denied, context)) + { + return true; + } + } + return false; + } + + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return true if granted + */ + private boolean isGranted(AccessControlEntry ace, Set authorisations, Set> denied, PermissionContext context) + { + // If the permission entry denies then we just deny + if (ace.getAccessStatus() == AccessStatus.DENIED) + { + denied.add(new Pair(ace.getAuthority(), ace.getPermission())); + + Set granters = modelDAO.getGrantingPermissions(ace.getPermission()); + for (PermissionReference granter : granters) + { + denied.add(new Pair(ace.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(ace.getPermission()); + for (PermissionReference grantee : grantees) + { + denied.add(new Pair(ace.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (ace.getPermission().equals(getAllPermissionReference()) || ace.getPermission().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions(context.getType(), context.getAspects())) + { + denied.add(new Pair(ace.getAuthority(), deny)); + } + } + + return false; + } + + // The permission is allowed but we deny it as it is in the denied + // set + + if (denied != null) + { + Pair specific = new Pair(ace.getAuthority(), required); + if (denied.contains(specific)) + { + return false; + } + } + + // any deny denies + + if (false) + { + if (denied != null) + { + for (String auth : authorisations) + { + Pair specific = new Pair(auth, required); + if (denied.contains(specific)) + { + return false; + } + for (PermissionReference perm : granters) + { + specific = new Pair(auth, perm); + if (denied.contains(specific)) + { + return false; + } + } + } + } + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authorisations.contains(ace.getAuthority()) && granters.contains(ace.getPermission())) + { + { + return true; + } + } + + // Default deny + return false; + } + + private boolean isGranted(PermissionEntry pe, Set authorisations) + { + // If the permission entry denies then we just deny + if (pe.isDenied()) + { + return false; + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (granters.contains(pe.getPermissionReference()) && authorisations.contains(pe.getAuthority())) + { + { + return true; + } + } + + // Default deny + return false; + } + + } + + /** + * Helper class to store a pair of objects which may be null + * + * @author Andy Hind + */ + private static class Pair + { + A a; + + B b; + + Pair(A a, B b) + { + this.a = a; + this.b = b; + } + + A getA() + { + return a; + } + + B getB() + { + return b; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(this instanceof Pair)) + { + return false; + } + Pair other = (Pair) o; + return EqualsHelper.nullSafeEquals(this.getA(), other.getA()) && EqualsHelper.nullSafeEquals(this.getB(), other.getB()); + } + + @Override + public int hashCode() + { + return (((a == null) ? 0 : a.hashCode()) * 37) + ((b == null) ? 0 : b.hashCode()); + } + + } + + private static class MutableBoolean + { + private boolean value; + + MutableBoolean(boolean value) + { + this.value = value; + } + + void setValue(boolean value) + { + this.value = value; + } + + boolean getValue() + { + return value; + } + } + + public Map> getAllSetPermissionsForCurrentUser() + { + String currentUser = AuthenticationUtil.getRunAsUser(); + return getAllSetPermissionsForAuthority(currentUser); + } + + public Map> getAllSetPermissionsForAuthority(String authority) + { + return permissionsDaoComponent.getAllSetPermissions(authority); + } + + public Set findNodesByAssignedPermissionForCurrentUser(String permission, boolean allow, boolean includeContainingAuthorities, boolean exactPermissionMatch) + { + String currentUser = AuthenticationUtil.getRunAsUser(); + return findNodesByAssignedPermission(currentUser, permission, allow, includeContainingAuthorities, exactPermissionMatch); + } + + public Set findNodesByAssignedPermission(String authority, String permission, boolean allow, boolean includeContainingAuthorities, boolean includeContainingPermissions) + { + // TODO: owned nodes and add owner rights ?? + // Does not include dynamic permissions (they would have to be done by query - e.g. owership and OWNER rights) + // Does not include ACEGI auth object authorities + Set authorities = new HashSet(); + authorities.add(authority); + if (includeContainingAuthorities) + { + authorities.addAll(authorityService.getAuthoritiesForUser(authority)); + } + + HashSet answer = new HashSet(); + + PermissionReference pr = getPermissionReference(permission); + Set permissions = new HashSet(); + permissions.add(pr); + + if (includeContainingPermissions) + { + permissions.addAll(modelDAO.getGrantingPermissions(pr)); + } + + for (PermissionReference perm : permissions) + { + for (String auth : authorities) + { + answer.addAll(permissionsDaoComponent.findNodeByPermission(auth, perm, allow)); + } + } + return answer; + } + + /** + * This methods checks weather the specified NodeRef instance is an VersionedNodeRef + * + * @param nodeRef - probably VersionedNodeRef + * @return true if NodeRef if Versioned and false in other case + */ + private boolean isVersionedNodeRefInstance(NodeRef nodeRef) + { + return nodeRef.getStoreRef().getProtocol().equals(VersionModel.STORE_PROTOCOL); + } + + /** + * Converts specified VersionedNodeRef to Frozen NodeRef (from SpacesStore store, accessed by workspace protocol) + * + * @param nodeRef - always VersionedNodeRef + * @return Frozen NodeRef instance (source for this VersionedNodeRef instance) + */ + private NodeRef convertVersionedNodeRefToFrozenNodeRef(NodeRef nodeRef) + { + + Map properties = nodeService.getProperties(nodeRef); + + return new NodeRef((String) properties.get(ContentModel.PROP_STORE_PROTOCOL), + (String) properties.get(ContentModel.PROP_STORE_IDENTIFIER), + (String) properties.get(ContentModel.PROP_NODE_UUID)); + } +} diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 3e6d28b4c8..3b25a0ff53 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -1,1528 +1,1528 @@ -/* - * Copyright (C) 2005-2009 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.repo.site; - -import java.io.Serializable; -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.concurrent.ConcurrentHashMap; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.activities.ActivityType; -import org.alfresco.repo.search.QueryParameterDefImpl; -import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.activities.ActivityService; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -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.QueryParameterDefinition; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.service.cmr.site.SiteVisibility; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; -import org.alfresco.util.PropertyMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Site Service Implementation. Also bootstraps the site AVM and DM stores. - * - * @author Roy Wetherall - */ -public class SiteServiceImpl implements SiteService, SiteModel -{ - /** Logger */ - private static Log logger = LogFactory.getLog(SiteServiceImpl.class); - - /** The DM store where site's are kept */ - public static final StoreRef SITE_STORE = new StoreRef("workspace://SpacesStore"); - - /** Activity tool */ - private static final String ACTIVITY_TOOL = "siteService"; - - private static final String SITE_PREFIX = "site_"; - private static final String GROUP_SITE_PREFIX = PermissionService.GROUP_PREFIX + SITE_PREFIX; - private static final int GROUP_SITE_PREFIX_LENGTH = GROUP_SITE_PREFIX.length(); - - /** Site home ref cache (Tennant aware) */ - private Map siteHomeRefs = new ConcurrentHashMap(4); - - /** Site node ref cache (Tennant aware) */ - private Map siteNodeRefs = new ConcurrentHashMap(256); - - private String sitesXPath; - - /** Messages */ - private static final String MSG_UNABLE_TO_CREATE = "site_service.unable_to_create"; - private static final String MSG_CAN_NOT_UPDATE = "site_service.can_not_update"; - private static final String MSG_CAN_NOT_DELETE = "site_service.can_not_delete"; - private static final String MSG_SITE_NO_EXIST = "site_service.site_no_exist"; - private static final String MSG_DO_NOT_REMOVE_MGR = "site_service.do_not_remove_manager"; - private static final String MSG_CAN_NOT_REMOVE_MSHIP = "site_service.can_not_reomve_memebership"; - private static final String MSG_DO_NOT_CHANGE_MGR = "site_service.do_not_change_manager"; - private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_memebership"; - private static final String MSG_SITE_CONTAINER_NOT_FOLDER = "site_service.site_container_not_folder"; - - /* Services */ - private NodeService nodeService; - private FileFolderService fileFolderService; - private SearchService searchService; - private NamespaceService namespaceService; - private PermissionService permissionService; - private ActivityService activityService; - private PersonService personService; - private AuthenticationComponent authenticationComponent; - private TaggingService taggingService; - private AuthorityService authorityService; - private DictionaryService dictionaryService; - private TenantService tenantService; - private TenantAdminService tenantAdminService; - private RetryingTransactionHelper retryingTransactionHelper; - - - /** - * Set the path to the location of the sites root folder. For example: - *
-     * ./app:company_home/st:sites
-     * 
- * @param sitesXPath a valid XPath - */ - public void setSitesXPath(String sitesXPath) - { - this.sitesXPath = sitesXPath; - } - - /** - * Set node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set file folder service - */ - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - /** - * Set search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Set Namespace service - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * Set permission service - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * Set activity service - */ - public void setActivityService(ActivityService activityService) - { - this.activityService = activityService; - } - - /** - * Set person service - */ - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - /** - * Set authentication component - */ - public void setAuthenticationComponent( - AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - /** - * Set the tagging service - */ - public void setTaggingService(TaggingService taggingService) - { - this.taggingService = taggingService; - } - - /** - * Set the authority service - */ - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - /** - * Set the dictionary service - * - * @param dictionaryService dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the tenant service - * - * @param tenantService tenant service - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - /** - * Sets the tenant admin service - */ - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - /** - * Sets helper that provides transaction callbacks - */ - public void setTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) - { - this.retryingTransactionHelper = retryingTransactionHelper; - } - - /** - * Checks that all necessary properties and services have been provided. - */ - public void init() - { - PropertyCheck.mandatory(this, "nodeService", nodeService); - PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); - PropertyCheck.mandatory(this, "searchService", searchService); - PropertyCheck.mandatory(this, "namespaceService", namespaceService); - PropertyCheck.mandatory(this, "permissionService", permissionService); - PropertyCheck.mandatory(this, "authenticationComponent", authenticationComponent); - PropertyCheck.mandatory(this, "personService", personService); - PropertyCheck.mandatory(this, "activityService", activityService); - PropertyCheck.mandatory(this, "taggingService", taggingService); - PropertyCheck.mandatory(this, "authorityService", authorityService); - PropertyCheck.mandatory(this, "sitesXPath", sitesXPath); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) - */ - public SiteInfo createSite( final String sitePreset, - String passedShortName, - final String title, - final String description, - final boolean isPublic) - { - // Determine the site visibility - SiteVisibility visibility = SiteVisibility.PRIVATE; - if (isPublic == true) - { - visibility = SiteVisibility.PUBLIC; - } - - // Create the site - return createSite(sitePreset, passedShortName, title, description, visibility); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) - */ - public SiteInfo createSite(final String sitePreset, - String passedShortName, - final String title, - final String description, - final SiteVisibility visibility) - { - // Remove spaces from shortName - final String shortName = passedShortName.replaceAll(" ", ""); - - // Check to see if we already have a site of this name - NodeRef existingSite = getSiteNodeRef(shortName); - if (existingSite != null) - { - // Throw an exception since we have a duplicate site name - throw new SiteServiceException(MSG_UNABLE_TO_CREATE, new Object[]{shortName}); - } - - // Get the site parent node reference - NodeRef siteParent = getSiteParent(shortName); - - // Create the site node - PropertyMap properties = new PropertyMap(4); - properties.put(ContentModel.PROP_NAME, shortName); - properties.put(SiteModel.PROP_SITE_PRESET, sitePreset); - properties.put(SiteModel.PROP_SITE_VISIBILITY, visibility.toString()); - properties.put(ContentModel.PROP_TITLE, title); - properties.put(ContentModel.PROP_DESCRIPTION, description); - - final NodeRef siteNodeRef = this.nodeService.createNode( - siteParent, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, - shortName), SiteModel.TYPE_SITE, properties) - .getChildRef(); - - // Make the new site a tag scope - this.taggingService.addTagScope(siteNodeRef); - - // Clear the sites inherited permissions - this.permissionService.setInheritParentPermissions(siteNodeRef, false); - - // Get the current user - final String currentUser = authenticationComponent.getCurrentUserName(); - - // Create the relevant groups and assign permissions - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public String doWork() throws Exception - { - // Create the site's groups - String siteGroup = authorityService.createAuthority( - AuthorityType.GROUP, null, getSiteGroup(shortName, - false)); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - for (String permission : permissions) - { - // Create a group for the permission - String permissionGroup = authorityService.createAuthority( - AuthorityType.GROUP, siteGroup, getSiteRoleGroup( - shortName, permission, false)); - - // Assign the group the relevant permission on the site - permissionService.setPermission(siteNodeRef, permissionGroup, permission, true); - } - - // Set the memberships details - // - give all authorities site consumer if site is public - // - give all authorities read properties if site is moderated - // - give all authorities read permission on permissions so - // memberships can be calculated - // - add the current user to the site manager group - if (SiteVisibility.PUBLIC.equals(visibility) == true) - { - permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); - } - else if (SiteVisibility.MODERATED.equals(visibility) == true) - { - permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES, true); - } - permissionService.setPermission(siteNodeRef, - PermissionService.ALL_AUTHORITIES, - PermissionService.READ_PERMISSIONS, true); - authorityService.addAuthority(getSiteRoleGroup(shortName, - SiteModel.SITE_MANAGER, true), currentUser); - - // Return nothing - return null; - } - - }, AuthenticationUtil.getSystemUserName()); - - // Return created site information - Map customProperties = getSiteCustomProperties(siteNodeRef); - SiteInfo siteInfo = new SiteInfoImpl(sitePreset, shortName, title, description, visibility, customProperties, siteNodeRef); - return siteInfo; - } - - /** - * Gets a map containing the site's custom properties - * - * @return Map map containing the custom properties of the site - */ - private Map getSiteCustomProperties(Map properties) - { - Map customProperties = new HashMap(4); - - for (Map.Entry entry : properties.entrySet()) - { - if (entry.getKey().getNamespaceURI().equals(SITE_CUSTOM_PROPERTY_URL) == true) - { - customProperties.put(entry.getKey(), entry.getValue()); - } - } - - return customProperties; - } - - /** - * Gets a map containing the site's custom properties - * - * @return Map map containing the custom properties of the site - */ - private Map getSiteCustomProperties(NodeRef siteNodeRef) - { - Map customProperties = new HashMap(4); - Map properties = nodeService.getProperties(siteNodeRef); - - for (Map.Entry entry : properties.entrySet()) - { - if (entry.getKey().getNamespaceURI().equals(SITE_CUSTOM_PROPERTY_URL) == true) - { - customProperties.put(entry.getKey(), entry.getValue()); - } - } - - return customProperties; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getSiteGroup(java.lang.String) - */ - public String getSiteGroup(String shortName) - { - return getSiteGroup(shortName, true); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getSiteRoleGroup(java.lang.String, - * java.lang.String) - */ - public String getSiteRoleGroup(String shortName, String role) - { - return getSiteRoleGroup(shortName, role, true); - } - - /** - * Helper method to get the name of the site group - * - * @param shortName site short name - * @return String site group name - */ - public String getSiteGroup(String shortName, boolean withGroupPrefix) - { - StringBuffer sb = new StringBuffer(64); - if (withGroupPrefix == true) - { - sb.append(PermissionService.GROUP_PREFIX); - } - sb.append(SITE_PREFIX); - sb.append(shortName); - return sb.toString(); - } - - /** - * Helper method to get the name of the site permission group - * - * @param shortName site short name - * @param permission permission name - * @return String site permission group name - */ - public String getSiteRoleGroup(String shortName, String permission, boolean withGroupPrefix) - { - return getSiteGroup(shortName, withGroupPrefix) + '_' + permission; - } - - /** - * Gets a sites parent folder based on it's short name ] - * - * @param shortName site short name - * @return NodeRef the site's parent - */ - private NodeRef getSiteParent(String shortName) - { - // TODO: For now just return the site root, later we may build folder - // structure based on the shortname to spread the sites about - return getSiteRoot(); - } - - /** - * Get the node reference that is the site root - * - * @return NodeRef node reference - */ - private NodeRef getSiteRoot() - { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - NodeRef siteHomeRef = siteHomeRefs.get(tenantDomain); - if (siteHomeRef == null) - { - siteHomeRef = AuthenticationUtil.runAs(new RunAsWork() - { - public NodeRef doWork() throws Exception - { - return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Exception - { - // Get the root 'sites' folder - NodeRef rootNodeRef = nodeService.getRootNode(SITE_STORE); - List results = searchService.selectNodes( - rootNodeRef, - sitesXPath, - null, - namespaceService, - false, - SearchService.LANGUAGE_XPATH); - if (results.size() == 0) - { - // No root site folder exists - throw new SiteServiceException("No root sites folder exists"); - } - else if (results.size() != 1) - { - // More than one root site folder exits - logger.warn("More than one root sites folder exists: \n" + results); - } - - return results.get(0); - } - }); - } - }, AuthenticationUtil.getSystemUserName()); - - siteHomeRefs.put(tenantDomain, siteHomeRef); - } - return siteHomeRef; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String, java.lang.String) - */ - public List listSites(String nameFilter, String sitePresetFilter) - { - return listSites(nameFilter, sitePresetFilter, 0); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String, java.lang.String, int) - */ - public List listSites(String nameFilter, String sitePresetFilter, int size) - { - List result; - - // TODO: take into consideration the sitePresetFilter - - NodeRef siteRoot = getSiteRoot(); - if (nameFilter != null && nameFilter.length() != 0) - { - // Perform a Lucene search under the Site parent node using *name*, *title* and *description* search query - QueryParameterDefinition[] params = new QueryParameterDefinition[3]; - params[0] = new QueryParameterDefImpl( - ContentModel.PROP_NAME, - dictionaryService.getDataType( - DataTypeDefinition.TEXT), - true, - LuceneQueryParser.escape(nameFilter.replace('"', ' '))); - - params[1] = new QueryParameterDefImpl( - ContentModel.PROP_TITLE, - dictionaryService.getDataType( - DataTypeDefinition.TEXT), - true, - LuceneQueryParser.escape(nameFilter.replace('"', ' '))); - - params[2] = new QueryParameterDefImpl( - ContentModel.PROP_DESCRIPTION, - dictionaryService.getDataType( - DataTypeDefinition.TEXT), - true, - LuceneQueryParser.escape(nameFilter.replace('"', ' '))); - - // get the sites that match the specified names - StringBuilder query = new StringBuilder(128); - query.append("+PARENT:\"").append(siteRoot.toString()) - .append("\" +(@cm\\:name:\"*${cm:name}*\"") - .append(" @cm\\:title:\"*${cm:title}*\"") - .append(" @cm\\:description:\"*${cm:description}*\")"); - ResultSet results = this.searchService.query( - siteRoot.getStoreRef(), - SearchService.LANGUAGE_LUCENE, - query.toString(), - params); - result = new ArrayList(results.length()); - try - { - for (NodeRef site : results.getNodeRefs()) - { - // Ignore any node type that is not a "site" - QName siteClassName = this.nodeService.getType(site); - if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE) == true) - { - result.add(createSiteInfo(site)); - // break on max size limit reached - if (result.size() == size) break; - } - } - } - finally - { - results.close(); - } - } - else - { - // Get ALL sites - this may be a very slow operation if there are many sites... - List assocs = this.nodeService.getChildAssocs( - siteRoot, ContentModel.ASSOC_CONTAINS, - RegexQNamePattern.MATCH_ALL); - result = new ArrayList(assocs.size()); - for (ChildAssociationRef assoc : assocs) - { - // Ignore any node type that is not a "site" - NodeRef site = assoc.getChildRef(); - QName siteClassName = this.nodeService.getType(site); - if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE) == true) - { - result.add(createSiteInfo(site)); - // break on max size limit reached - if (result.size() == size) break; - } - } - } - - return result; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String) - */ - public List listSites(String userName) - { - // get the Groups this user is contained within (at any level) - Set groups = this.authorityService.getContainingAuthorities(null, userName, false); - Set siteNames = new HashSet(groups.size()); - // purge non Site related Groups and strip the group name down to the site "shortName" it relates too - for (String group : groups) - { - if (group.startsWith(GROUP_SITE_PREFIX)) - { - int roleIndex = group.lastIndexOf('_'); - String siteName; - if (roleIndex + 1 <= GROUP_SITE_PREFIX_LENGTH) - { - // There is no role associated - siteName = group.substring(GROUP_SITE_PREFIX_LENGTH); - } - else - { - siteName = group.substring(GROUP_SITE_PREFIX_LENGTH, roleIndex); - } - siteNames.add(siteName); - } - } - - // retrieve the site nodes based on the list from the containing site groups - NodeRef siteRoot = getSiteRoot(); - List siteList = new ArrayList(siteNames); - // ensure we do not trip over the getChildrenByName() 1000 item API limit! - if (siteList.size() > 1000) - { - siteList = siteList.subList(0, 1000); - } - List assocs = this.nodeService.getChildrenByName( - siteRoot, - ContentModel.ASSOC_CONTAINS, - siteList); - List result = new ArrayList(assocs.size()); - for (ChildAssociationRef assoc : assocs) - { - // Ignore any node that is not a "site" type - NodeRef site = assoc.getChildRef(); - QName siteClassName = this.nodeService.getType(site); - if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE)) - { - result.add(createSiteInfo(site)); - } - } - - return result; - } - - /** - * Creates a site information object given a site node reference - * - * @param siteNodeRef - * site node reference - * @return SiteInfo site information object - */ - private SiteInfo createSiteInfo(NodeRef siteNodeRef) - { - // Get the properties - Map properties = this.nodeService.getProperties(siteNodeRef); - String shortName = (String) properties.get(ContentModel.PROP_NAME); - String sitePreset = (String) properties.get(PROP_SITE_PRESET); - String title = (String) properties.get(ContentModel.PROP_TITLE); - String description = (String) properties.get(ContentModel.PROP_DESCRIPTION); - - // Get the visibility of the site - SiteVisibility visibility = getSiteVisibility(siteNodeRef); - - // Create and return the site information - Map customProperties = getSiteCustomProperties(properties); - SiteInfo siteInfo = new SiteInfoImpl(sitePreset, shortName, title, description, visibility, customProperties, siteNodeRef); - return siteInfo; - } - - /** - * Helper method to get the visibility of the site. If no value is present in the repository then it is calculated from the - * set permissions. This will maintain backwards compatibility with earlier versions of the service implementation. - * - * @param siteNodeRef site node reference - * @return SiteVisibility site visibility - */ - private SiteVisibility getSiteVisibility(NodeRef siteNodeRef) - { - SiteVisibility visibility = SiteVisibility.PRIVATE; - - // Get the visibility value stored in the repo - String visibilityValue = (String)this.nodeService.getProperty(siteNodeRef, SiteModel.PROP_SITE_VISIBILITY); - - // To maintain backwards compatibility calculate the visibility from the permissions - // if there is no value specified on the site node - if (visibilityValue == null) - { - // Examine each permission to see if this is a public site or not - Set permissions = this.permissionService.getAllSetPermissions(siteNodeRef); - for (AccessPermission permission : permissions) - { - if (permission.getAuthority().equals(PermissionService.ALL_AUTHORITIES) == true && - permission.getPermission().equals(SITE_CONSUMER) == true) - { - visibility = SiteVisibility.PUBLIC; - break; - } - } - } - else - { - // Create the enum value from the string - visibility = SiteVisibility.valueOf(visibilityValue); - } - - return visibility; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getSite(java.lang.String) - */ - public SiteInfo getSite(final String shortName) - { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) - { - final String tenantDomain = tenantService.getDomain(shortName); - final String sName = tenantService.getBaseName(shortName, true); - - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public SiteInfo doWork() throws Exception - { - SiteInfo site = getSiteImpl(sName); - return new SiteInfoImpl(site.getSitePreset(), shortName, site.getTitle(), site.getDescription(), site.getVisibility(), site.getCustomProperties(), site.getNodeRef()); - } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - } - else - { - return getSiteImpl(shortName); - } - } - - private SiteInfo getSiteImpl(String shortName) - { - SiteInfo result = null; - - // Get the site node - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef != null) - { - // Create the site info - result = createSiteInfo(siteNodeRef); - } - - // Return the site information - return result; - } - - /** - * Gets the site's node reference based on its short name - * - * @param shortName short name - * - * @return NodeRef node reference - */ - private NodeRef getSiteNodeRef(String shortName) - { - final String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; - NodeRef siteNodeRef = this.siteNodeRefs.get(cacheKey); - if (siteNodeRef != null) - { - // test for existance - and remove from cache if no longer exists - if (!this.nodeService.exists(siteNodeRef)) - { - this.siteNodeRefs.remove(cacheKey); - siteNodeRef = null; - } - } - else - { - // not in cache - find and store - NodeRef siteRoot = getSiteParent(shortName); - - // the site "short name" directly maps to the cm:name property - siteNodeRef = this.nodeService.getChildByName(siteRoot, ContentModel.ASSOC_CONTAINS, shortName); - - // cache the result if found - null results will be requeried to ensure new sites are found later - if (siteNodeRef != null) - { - this.siteNodeRefs.put(cacheKey, siteNodeRef); - } - } - return siteNodeRef; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#updateSite(org.alfresco.service.cmr.site.SiteInfo) - */ - public void updateSite(SiteInfo siteInfo) - { - NodeRef siteNodeRef = getSiteNodeRef(siteInfo.getShortName()); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_CAN_NOT_UPDATE, new Object[]{siteInfo.getShortName()}); - } - - // Get the sites properties - Map properties = this.nodeService.getProperties(siteNodeRef); - - // Update the properties of the site - // Note: the site preset and short name should never be updated! - properties.put(ContentModel.PROP_TITLE, siteInfo.getTitle()); - properties.put(ContentModel.PROP_DESCRIPTION, siteInfo.getDescription()); - - // Update the isPublic flag - SiteVisibility currentVisibility = getSiteVisibility(siteNodeRef); - SiteVisibility updatedVisibility = siteInfo.getVisibility(); - if (currentVisibility.equals(updatedVisibility) == false) - { - // Remove current visibility permissions - if (SiteVisibility.PUBLIC.equals(currentVisibility) == true) - { - this.permissionService.deletePermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER); - } - else if (SiteVisibility.MODERATED.equals(currentVisibility) == true) - { - this.permissionService.deletePermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES); - // TODO update all child folders ?? ... - } - - // Add new visibility permissions - if (SiteVisibility.PUBLIC.equals(updatedVisibility) == true) - { - this.permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); - } - else if (SiteVisibility.MODERATED.equals(updatedVisibility) == true) - { - this.permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES, true); - // TODO update all child folders ?? ... - } - - // Update the site node reference with the updated visibility value - properties.put(SiteModel.PROP_SITE_VISIBILITY, siteInfo.getVisibility()); - } - - // Set the updated properties back onto the site node reference - this.nodeService.setProperties(siteNodeRef, properties); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#deleteSite(java.lang.String) - */ - public void deleteSite(final String shortName) - { - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_CAN_NOT_DELETE, new Object[]{shortName}); - } - - // Delete the cached reference - String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; - this.siteNodeRefs.remove(cacheKey); - - // Delete the node - this.nodeService.deleteNode(siteNodeRef); - - // Delete the associated group's - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - authorityService.deleteAuthority(getSiteGroup(shortName, true)); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#listMembers(java.lang.String, java.lang.String, java.lang.String, int) - */ - public Map listMembers(String shortName, String nameFilter, String roleFilter, int size) - { - return listMembers(shortName, nameFilter, roleFilter, size, false); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#listMembers(String, String, String, int, boolean) - */ - public Map listMembers(String shortName, final String nameFilter, final String roleFilter, final int size, final boolean collapseGroups) - { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) - { - final String tenantDomain = tenantService.getDomain(shortName); - final String sName = tenantService.getBaseName(shortName, true); - - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() - { - public Map doWork() throws Exception - { - return listMembersImpl(sName, nameFilter, roleFilter, size, collapseGroups); - } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - } - else - { - return listMembersImpl(shortName, nameFilter, roleFilter, size, collapseGroups); - } - } - - private Map listMembersImpl(String shortName, String nameFilter, String roleFilter, int size, boolean collapseGroups) - { - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - Map members = new HashMap(32); - - Set permissions = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - for (String permission : permissions) - { - if (roleFilter == null || roleFilter.length() == 0 || roleFilter.equals(permission)) - { - String groupName = getSiteRoleGroup(shortName, permission, true); - Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, groupName, true); - for (String user : users) - { - boolean addUser = true; - if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(user)) - { - // found a filter - does it match person first/last name? - addUser = matchPerson(nameFilter, user); - } - if (addUser) - { - // Add the user and their permission to the returned map - members.put(user, permission); - - // break on max size limit reached - if (members.size() == size) break; - } - } - - Set groups = this.authorityService.getContainedAuthorities(AuthorityType.GROUP, groupName, true); - for (String group : groups) - { - if (collapseGroups == false) - { - if (nameFilter != null && nameFilter.length() != 0) - { - // found a filter - does it match Group name part? - if (group.substring(GROUP_SITE_PREFIX_LENGTH).toLowerCase().contains(nameFilter.toLowerCase())) - { - members.put(group, permission); - } - } - } - else - { - Set subUsers = this.authorityService.getContainedAuthorities(AuthorityType.USER, group, false); - for (String subUser : subUsers) - { - boolean addUser = true; - if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(subUser)) - { - // found a filter - does it match person first/last name? - addUser = matchPerson(nameFilter, subUser); - } - if (addUser) - { - // Add the collapsed user into the members list if they do not already appear in the list - if (members.containsKey(subUser) == false) - { - members.put(subUser, permission); - } - - // break on max size limit reached - if (members.size() == size) break; - } - } - } - } - } - } - - return members; - } - - /** - * Helper to - * @param filter - * @param username - * @return - */ - private boolean matchPerson(String filter, String username) - { - boolean addUser = false; - NodeRef personRef = this.personService.getPerson(username); - Map props = this.nodeService.getProperties(personRef); - String firstName = (String)props.get(ContentModel.PROP_FIRSTNAME); - String lastName = (String)props.get(ContentModel.PROP_LASTNAME); - if (firstName != null && firstName.toLowerCase().indexOf(filter.toLowerCase()) != -1) - { - addUser = true; - } - else if (lastName != null && lastName.toLowerCase().indexOf(filter.toLowerCase()) != -1) - { - addUser = true; - } - return addUser; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getMembersRole(java.lang.String, - * java.lang.String) - */ - public String getMembersRole(String shortName, String authorityName) - { - String result = null; - List roles = getMembersRoles(shortName, authorityName); - if (roles.isEmpty() == false) - { - result = roles.get(0); - } - return result; - } - - public List getMembersRoles(String shortName, String authorityName) - { - List result = new ArrayList(5); - List groups = getPermissionGroups(shortName, authorityName); - for (String group : groups) - { - int index = group.lastIndexOf('_'); - if (index != -1) - { - result.add(group.substring(index + 1)); - } - } - return result; - } - - /** - * Helper method to get the permission groups for a given authority on a site. - * Returns empty List if the user does not have a explicit membership to the site. - * - * @param siteShortName site short name - * @param authorityName authority name - * @return List Permission groups, empty list if no explicit membership set - */ - private List getPermissionGroups(String siteShortName, String authorityName) - { - List result = new ArrayList(5); - Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - for (String role : roles) - { - String roleGroup = getSiteRoleGroup(siteShortName, role, true); - Set authorities = this.authorityService.getContainedAuthorities(null, roleGroup, false); - if (authorities.contains(authorityName) == true) - { - result.add(roleGroup); - } - } - return result; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles() - */ - public List getSiteRoles() - { - Set permissions = permissionService - .getSettablePermissions(SiteModel.TYPE_SITE); - return new ArrayList(permissions); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#isMember(java.lang.String, java.lang.String) - */ - public boolean isMember(String shortName, String authorityName) - { - return (!getPermissionGroups(shortName, authorityName).isEmpty()); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#removeMembership(java.lang.String, java.lang.String) - */ - public void removeMembership(final String shortName, final String authorityName) - { - final NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - // TODO what do we do about the user if they are in a group that has - // rights to the site? - - // Get the current user - String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - - // Get the user current role - final String role = getMembersRole(shortName, authorityName); - if (role != null) - { - // Check that we are not about to remove the last site manager - if (SiteModel.SITE_MANAGER.equals(role) == true) - { - Set siteMangers = this.authorityService - .getContainedAuthorities( - AuthorityType.USER, - getSiteRoleGroup(shortName, SITE_MANAGER, true), - true); - if (siteMangers.size() == 1) - { - throw new SiteServiceException(MSG_DO_NOT_REMOVE_MGR, new Object[]{authorityName}); - } - } - - // If ... - // -- the current user has change permissions rights on the site - // or - // -- the user is ourselves - if ((currentUserName.equals(authorityName) == true) || - (permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED)) - { - // Run as system user - AuthenticationUtil.runAs( - new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - // Remove the user from the current permission - // group - String currentGroup = getSiteRoleGroup(shortName, role, true); - authorityService.removeAuthority(currentGroup, authorityName); - - return null; - } - }, AuthenticationUtil.SYSTEM_USER_NAME); - - // Raise events - if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) - { - activityService.postActivity( - ActivityType.SITE_USER_REMOVED, shortName, - ACTIVITY_TOOL, getActivityData(authorityName, "")); - } - else - { - // TODO - update this, if sites support groups - logger.error("setMembership - failed to post activity: unexpected authority type: " - + AuthorityType.getAuthorityType(authorityName)); - } - } - else - { - // Throw an exception - throw new SiteServiceException(MSG_CAN_NOT_REMOVE_MSHIP, new Object[]{shortName}); - } - } - else - { - // Throw an exception - throw new SiteServiceException(MSG_CAN_NOT_REMOVE_MSHIP, new Object[]{shortName}); - } - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#setMembership(java.lang.String, - * java.lang.String, java.lang.String) - */ - public void setMembership(final String shortName, - final String authorityName, - final String role) - { - final NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - // Get the user's current role - final String currentRole = getMembersRole(shortName, authorityName); - - // Do nothing if the role of the user is not being changed - if (currentRole == null || role.equals(currentRole) == false) - { - // TODO if this is the only site manager do not down grade their - // permissions - - // Get the visibility of the site - SiteVisibility visibility = getSiteVisibility(siteNodeRef); - - // If we are ... - // -- the current user has change permissions rights on the site - // or we are ... - // -- referring to a public site and - // -- the role being set is consumer and - // -- the user being added is ourselves and - // -- the member does not already have permissions - // ... then we can set the permissions as system user - final String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if ((permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) || - (SiteVisibility.PUBLIC.equals(visibility) == true && - role.equals(SiteModel.SITE_CONSUMER) == true && - authorityName.equals(currentUserName) == true && - currentRole == null)) - { - // Check that we are not about to remove the last site manager - if (SiteModel.SITE_MANAGER.equals(currentRole) == true) - { - Set siteMangers = this.authorityService.getContainedAuthorities(AuthorityType.USER, - getSiteRoleGroup(shortName, SITE_MANAGER, true), - true); - if (siteMangers.size() == 1) - { - throw new SiteServiceException(MSG_DO_NOT_CHANGE_MGR, new Object[]{authorityName}); - } - } - - // Run as system user - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - if (currentRole != null) - { - // Remove the user from the current - // permission group - String currentGroup = getSiteRoleGroup(shortName, currentRole, true); - authorityService.removeAuthority(currentGroup, authorityName); - } - - // Add the user to the new permission group - String newGroup = getSiteRoleGroup(shortName, role, true); - authorityService.addAuthority(newGroup, authorityName); - - return null; - } - - }, AuthenticationUtil.SYSTEM_USER_NAME); - - if (currentRole == null) - { - if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) - { - activityService.postActivity( - ActivityType.SITE_USER_JOINED, shortName, - ACTIVITY_TOOL, getActivityData(authorityName, role)); - } - else - { - // TODO - update this, if sites support groups - logger - .error("setMembership - failed to post activity: unexpected authority type: " - + AuthorityType.getAuthorityType(authorityName)); - } - } - else - { - if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) - { - activityService.postActivity( - ActivityType.SITE_USER_ROLE_UPDATE, shortName, - ACTIVITY_TOOL, getActivityData(authorityName, role)); - } - else - { - // TODO - update this, if sites support groups - logger.error("setMembership - failed to post activity: unexpected authority type: " - + AuthorityType.getAuthorityType(authorityName)); - } - } - } - else - { - // Raise a permission exception - throw new SiteServiceException(MSG_CAN_NOT_CHANGE_MSHIP, new Object[]{shortName}); - } - } - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#createContainer(java.lang.String, - * java.lang.String, org.alfresco.service.namespace.QName, - * java.util.Map) - */ - public NodeRef createContainer(String shortName, - String componentId, - QName containerType, - Map containerProperties) - { - // Check for the component id - ParameterCheck.mandatoryString("componentId", componentId); - - // retrieve site - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - // retrieve component folder within site - NodeRef containerNodeRef = null; - try - { - containerNodeRef = findContainer(siteNodeRef, componentId); - } - catch (FileNotFoundException e) - { - } - - // create the container node reference - if (containerNodeRef == null) - { - if (containerType == null) - { - containerType = ContentModel.TYPE_FOLDER; - } - - // create component folder - FileInfo fileInfo = fileFolderService.create(siteNodeRef, - componentId, containerType); - - // Get the created container - containerNodeRef = fileInfo.getNodeRef(); - - // Set the properties if they have been provided - if (containerProperties != null) - { - Map props = this.nodeService - .getProperties(containerNodeRef); - props.putAll(containerProperties); - this.nodeService.setProperties(containerNodeRef, props); - } - - // Add the container aspect - Map aspectProps = new HashMap( - 1); - aspectProps.put(SiteModel.PROP_COMPONENT_ID, componentId); - this.nodeService.addAspect(containerNodeRef, ASPECT_SITE_CONTAINER, - aspectProps); - - // Make the container a tag scope - this.taggingService.addTagScope(containerNodeRef); - } - - return containerNodeRef; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getContainer(java.lang.String) - */ - public NodeRef getContainer(String shortName, String componentId) - { - ParameterCheck.mandatoryString("componentId", componentId); - - // retrieve site - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - // retrieve component folder within site - // NOTE: component id is used for folder name - NodeRef containerNodeRef = null; - try - { - containerNodeRef = findContainer(siteNodeRef, componentId); - } - catch (FileNotFoundException e) - { - } - - return containerNodeRef; - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#hasContainer(java.lang.String) - */ - public boolean hasContainer(String shortName, String componentId) - { - ParameterCheck.mandatoryString("componentId", componentId); - - // retrieve site - NodeRef siteNodeRef = getSiteNodeRef(shortName); - if (siteNodeRef == null) - { - throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); - } - - // retrieve component folder within site - // NOTE: component id is used for folder name - boolean hasContainer = false; - try - { - findContainer(siteNodeRef, componentId); - hasContainer = true; - } - catch (FileNotFoundException e) - { - } - - return hasContainer; - } - - /** - * Locate site "container" folder for component - * - * @param siteNodeRef - * site - * @param componentId - * component id - * @return "container" node ref, if it exists - * @throws FileNotFoundException - */ - private NodeRef findContainer(NodeRef siteNodeRef, String componentId) - throws FileNotFoundException - { - List paths = new ArrayList(1); - paths.add(componentId); - FileInfo fileInfo = fileFolderService.resolveNamePath(siteNodeRef, - paths); - if (!fileInfo.isFolder()) - { - throw new SiteServiceException(MSG_SITE_CONTAINER_NOT_FOLDER, new Object[]{fileInfo.getName()}); - } - return fileInfo.getNodeRef(); - } - - /** - * Helper method to get the activity data for a user - * - * @param userName user name - * @param role role - * @return - */ - private String getActivityData(String userName, String role) - { - String memberFN = ""; - String memberLN = ""; - NodeRef person = personService.getPerson(userName); - if (person != null) - { - memberFN = (String) nodeService.getProperty(person, - ContentModel.PROP_FIRSTNAME); - memberLN = (String) nodeService.getProperty(person, - ContentModel.PROP_LASTNAME); - } - - try - { - JSONObject activityData = new JSONObject(); - activityData.put("role", role); - activityData.put("memberUserName", userName); - activityData.put("memberFirstName", memberFN); - activityData.put("memberLastName", memberLN); - activityData.put("title", (memberFN + " " + memberLN + " (" - + userName + ")").trim()); - return activityData.toString(); - } catch (JSONException je) - { - // log error, subsume exception - logger.error("Failed to get activity data: " + je); - return ""; - } - } -} +/* + * Copyright (C) 2005-2009 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.repo.site; + +import java.io.Serializable; +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.concurrent.ConcurrentHashMap; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.activities.ActivityType; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.QueryParameterDefinition; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Site Service Implementation. Also bootstraps the site AVM and DM stores. + * + * @author Roy Wetherall + */ +public class SiteServiceImpl implements SiteService, SiteModel +{ + /** Logger */ + private static Log logger = LogFactory.getLog(SiteServiceImpl.class); + + /** The DM store where site's are kept */ + public static final StoreRef SITE_STORE = new StoreRef("workspace://SpacesStore"); + + /** Activity tool */ + private static final String ACTIVITY_TOOL = "siteService"; + + private static final String SITE_PREFIX = "site_"; + private static final String GROUP_SITE_PREFIX = PermissionService.GROUP_PREFIX + SITE_PREFIX; + private static final int GROUP_SITE_PREFIX_LENGTH = GROUP_SITE_PREFIX.length(); + + /** Site home ref cache (Tennant aware) */ + private Map siteHomeRefs = new ConcurrentHashMap(4); + + /** Site node ref cache (Tennant aware) */ + private Map siteNodeRefs = new ConcurrentHashMap(256); + + private String sitesXPath; + + /** Messages */ + private static final String MSG_UNABLE_TO_CREATE = "site_service.unable_to_create"; + private static final String MSG_CAN_NOT_UPDATE = "site_service.can_not_update"; + private static final String MSG_CAN_NOT_DELETE = "site_service.can_not_delete"; + private static final String MSG_SITE_NO_EXIST = "site_service.site_no_exist"; + private static final String MSG_DO_NOT_REMOVE_MGR = "site_service.do_not_remove_manager"; + private static final String MSG_CAN_NOT_REMOVE_MSHIP = "site_service.can_not_reomve_memebership"; + private static final String MSG_DO_NOT_CHANGE_MGR = "site_service.do_not_change_manager"; + private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_memebership"; + private static final String MSG_SITE_CONTAINER_NOT_FOLDER = "site_service.site_container_not_folder"; + + /* Services */ + private NodeService nodeService; + private FileFolderService fileFolderService; + private SearchService searchService; + private NamespaceService namespaceService; + private PermissionService permissionService; + private ActivityService activityService; + private PersonService personService; + private AuthenticationContext authenticationContext; + private TaggingService taggingService; + private AuthorityService authorityService; + private DictionaryService dictionaryService; + private TenantService tenantService; + private TenantAdminService tenantAdminService; + private RetryingTransactionHelper retryingTransactionHelper; + + + /** + * Set the path to the location of the sites root folder. For example: + *
+     * ./app:company_home/st:sites
+     * 
+ * @param sitesXPath a valid XPath + */ + public void setSitesXPath(String sitesXPath) + { + this.sitesXPath = sitesXPath; + } + + /** + * Set node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set file folder service + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Set search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Set Namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Set permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Set activity service + */ + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + /** + * Set person service + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Set authentication component + */ + public void setAuthenticationContext( + AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Set the tagging service + */ + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + /** + * Set the authority service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * Set the dictionary service + * + * @param dictionaryService dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the tenant service + * + * @param tenantService tenant service + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Sets the tenant admin service + */ + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + /** + * Sets helper that provides transaction callbacks + */ + public void setTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + + /** + * Checks that all necessary properties and services have been provided. + */ + public void init() + { + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + PropertyCheck.mandatory(this, "searchService", searchService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "permissionService", permissionService); + PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); + PropertyCheck.mandatory(this, "personService", personService); + PropertyCheck.mandatory(this, "activityService", activityService); + PropertyCheck.mandatory(this, "taggingService", taggingService); + PropertyCheck.mandatory(this, "authorityService", authorityService); + PropertyCheck.mandatory(this, "sitesXPath", sitesXPath); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) + */ + public SiteInfo createSite( final String sitePreset, + String passedShortName, + final String title, + final String description, + final boolean isPublic) + { + // Determine the site visibility + SiteVisibility visibility = SiteVisibility.PRIVATE; + if (isPublic == true) + { + visibility = SiteVisibility.PUBLIC; + } + + // Create the site + return createSite(sitePreset, passedShortName, title, description, visibility); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) + */ + public SiteInfo createSite(final String sitePreset, + String passedShortName, + final String title, + final String description, + final SiteVisibility visibility) + { + // Remove spaces from shortName + final String shortName = passedShortName.replaceAll(" ", ""); + + // Check to see if we already have a site of this name + NodeRef existingSite = getSiteNodeRef(shortName); + if (existingSite != null) + { + // Throw an exception since we have a duplicate site name + throw new SiteServiceException(MSG_UNABLE_TO_CREATE, new Object[]{shortName}); + } + + // Get the site parent node reference + NodeRef siteParent = getSiteParent(shortName); + + // Create the site node + PropertyMap properties = new PropertyMap(4); + properties.put(ContentModel.PROP_NAME, shortName); + properties.put(SiteModel.PROP_SITE_PRESET, sitePreset); + properties.put(SiteModel.PROP_SITE_VISIBILITY, visibility.toString()); + properties.put(ContentModel.PROP_TITLE, title); + properties.put(ContentModel.PROP_DESCRIPTION, description); + + final NodeRef siteNodeRef = this.nodeService.createNode( + siteParent, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + shortName), SiteModel.TYPE_SITE, properties) + .getChildRef(); + + // Make the new site a tag scope + this.taggingService.addTagScope(siteNodeRef); + + // Clear the sites inherited permissions + this.permissionService.setInheritParentPermissions(siteNodeRef, false); + + // Get the current user + final String currentUser = authenticationContext.getCurrentUserName(); + + // Create the relevant groups and assign permissions + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public String doWork() throws Exception + { + // Create the site's groups + String siteGroup = authorityService.createAuthority( + AuthorityType.GROUP, null, getSiteGroup(shortName, + false)); + Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + for (String permission : permissions) + { + // Create a group for the permission + String permissionGroup = authorityService.createAuthority( + AuthorityType.GROUP, siteGroup, getSiteRoleGroup( + shortName, permission, false)); + + // Assign the group the relevant permission on the site + permissionService.setPermission(siteNodeRef, permissionGroup, permission, true); + } + + // Set the memberships details + // - give all authorities site consumer if site is public + // - give all authorities read properties if site is moderated + // - give all authorities read permission on permissions so + // memberships can be calculated + // - add the current user to the site manager group + if (SiteVisibility.PUBLIC.equals(visibility) == true) + { + permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); + } + else if (SiteVisibility.MODERATED.equals(visibility) == true) + { + permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES, true); + } + permissionService.setPermission(siteNodeRef, + PermissionService.ALL_AUTHORITIES, + PermissionService.READ_PERMISSIONS, true); + authorityService.addAuthority(getSiteRoleGroup(shortName, + SiteModel.SITE_MANAGER, true), currentUser); + + // Return nothing + return null; + } + + }, AuthenticationUtil.getSystemUserName()); + + // Return created site information + Map customProperties = getSiteCustomProperties(siteNodeRef); + SiteInfo siteInfo = new SiteInfoImpl(sitePreset, shortName, title, description, visibility, customProperties, siteNodeRef); + return siteInfo; + } + + /** + * Gets a map containing the site's custom properties + * + * @return Map map containing the custom properties of the site + */ + private Map getSiteCustomProperties(Map properties) + { + Map customProperties = new HashMap(4); + + for (Map.Entry entry : properties.entrySet()) + { + if (entry.getKey().getNamespaceURI().equals(SITE_CUSTOM_PROPERTY_URL) == true) + { + customProperties.put(entry.getKey(), entry.getValue()); + } + } + + return customProperties; + } + + /** + * Gets a map containing the site's custom properties + * + * @return Map map containing the custom properties of the site + */ + private Map getSiteCustomProperties(NodeRef siteNodeRef) + { + Map customProperties = new HashMap(4); + Map properties = nodeService.getProperties(siteNodeRef); + + for (Map.Entry entry : properties.entrySet()) + { + if (entry.getKey().getNamespaceURI().equals(SITE_CUSTOM_PROPERTY_URL) == true) + { + customProperties.put(entry.getKey(), entry.getValue()); + } + } + + return customProperties; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteGroup(java.lang.String) + */ + public String getSiteGroup(String shortName) + { + return getSiteGroup(shortName, true); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoleGroup(java.lang.String, + * java.lang.String) + */ + public String getSiteRoleGroup(String shortName, String role) + { + return getSiteRoleGroup(shortName, role, true); + } + + /** + * Helper method to get the name of the site group + * + * @param shortName site short name + * @return String site group name + */ + public String getSiteGroup(String shortName, boolean withGroupPrefix) + { + StringBuffer sb = new StringBuffer(64); + if (withGroupPrefix == true) + { + sb.append(PermissionService.GROUP_PREFIX); + } + sb.append(SITE_PREFIX); + sb.append(shortName); + return sb.toString(); + } + + /** + * Helper method to get the name of the site permission group + * + * @param shortName site short name + * @param permission permission name + * @return String site permission group name + */ + public String getSiteRoleGroup(String shortName, String permission, boolean withGroupPrefix) + { + return getSiteGroup(shortName, withGroupPrefix) + '_' + permission; + } + + /** + * Gets a sites parent folder based on it's short name ] + * + * @param shortName site short name + * @return NodeRef the site's parent + */ + private NodeRef getSiteParent(String shortName) + { + // TODO: For now just return the site root, later we may build folder + // structure based on the shortname to spread the sites about + return getSiteRoot(); + } + + /** + * Get the node reference that is the site root + * + * @return NodeRef node reference + */ + private NodeRef getSiteRoot() + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + NodeRef siteHomeRef = siteHomeRefs.get(tenantDomain); + if (siteHomeRef == null) + { + siteHomeRef = AuthenticationUtil.runAs(new RunAsWork() + { + public NodeRef doWork() throws Exception + { + return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + // Get the root 'sites' folder + NodeRef rootNodeRef = nodeService.getRootNode(SITE_STORE); + List results = searchService.selectNodes( + rootNodeRef, + sitesXPath, + null, + namespaceService, + false, + SearchService.LANGUAGE_XPATH); + if (results.size() == 0) + { + // No root site folder exists + throw new SiteServiceException("No root sites folder exists"); + } + else if (results.size() != 1) + { + // More than one root site folder exits + logger.warn("More than one root sites folder exists: \n" + results); + } + + return results.get(0); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + + siteHomeRefs.put(tenantDomain, siteHomeRef); + } + return siteHomeRef; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String, java.lang.String) + */ + public List listSites(String nameFilter, String sitePresetFilter) + { + return listSites(nameFilter, sitePresetFilter, 0); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String, java.lang.String, int) + */ + public List listSites(String nameFilter, String sitePresetFilter, int size) + { + List result; + + // TODO: take into consideration the sitePresetFilter + + NodeRef siteRoot = getSiteRoot(); + if (nameFilter != null && nameFilter.length() != 0) + { + // Perform a Lucene search under the Site parent node using *name*, *title* and *description* search query + QueryParameterDefinition[] params = new QueryParameterDefinition[3]; + params[0] = new QueryParameterDefImpl( + ContentModel.PROP_NAME, + dictionaryService.getDataType( + DataTypeDefinition.TEXT), + true, + LuceneQueryParser.escape(nameFilter.replace('"', ' '))); + + params[1] = new QueryParameterDefImpl( + ContentModel.PROP_TITLE, + dictionaryService.getDataType( + DataTypeDefinition.TEXT), + true, + LuceneQueryParser.escape(nameFilter.replace('"', ' '))); + + params[2] = new QueryParameterDefImpl( + ContentModel.PROP_DESCRIPTION, + dictionaryService.getDataType( + DataTypeDefinition.TEXT), + true, + LuceneQueryParser.escape(nameFilter.replace('"', ' '))); + + // get the sites that match the specified names + StringBuilder query = new StringBuilder(128); + query.append("+PARENT:\"").append(siteRoot.toString()) + .append("\" +(@cm\\:name:\"*${cm:name}*\"") + .append(" @cm\\:title:\"*${cm:title}*\"") + .append(" @cm\\:description:\"*${cm:description}*\")"); + ResultSet results = this.searchService.query( + siteRoot.getStoreRef(), + SearchService.LANGUAGE_LUCENE, + query.toString(), + params); + result = new ArrayList(results.length()); + try + { + for (NodeRef site : results.getNodeRefs()) + { + // Ignore any node type that is not a "site" + QName siteClassName = this.nodeService.getType(site); + if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE) == true) + { + result.add(createSiteInfo(site)); + // break on max size limit reached + if (result.size() == size) break; + } + } + } + finally + { + results.close(); + } + } + else + { + // Get ALL sites - this may be a very slow operation if there are many sites... + List assocs = this.nodeService.getChildAssocs( + siteRoot, ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL); + result = new ArrayList(assocs.size()); + for (ChildAssociationRef assoc : assocs) + { + // Ignore any node type that is not a "site" + NodeRef site = assoc.getChildRef(); + QName siteClassName = this.nodeService.getType(site); + if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE) == true) + { + result.add(createSiteInfo(site)); + // break on max size limit reached + if (result.size() == size) break; + } + } + } + + return result; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#listSites(java.lang.String) + */ + public List listSites(String userName) + { + // get the Groups this user is contained within (at any level) + Set groups = this.authorityService.getContainingAuthorities(null, userName, false); + Set siteNames = new HashSet(groups.size()); + // purge non Site related Groups and strip the group name down to the site "shortName" it relates too + for (String group : groups) + { + if (group.startsWith(GROUP_SITE_PREFIX)) + { + int roleIndex = group.lastIndexOf('_'); + String siteName; + if (roleIndex + 1 <= GROUP_SITE_PREFIX_LENGTH) + { + // There is no role associated + siteName = group.substring(GROUP_SITE_PREFIX_LENGTH); + } + else + { + siteName = group.substring(GROUP_SITE_PREFIX_LENGTH, roleIndex); + } + siteNames.add(siteName); + } + } + + // retrieve the site nodes based on the list from the containing site groups + NodeRef siteRoot = getSiteRoot(); + List siteList = new ArrayList(siteNames); + // ensure we do not trip over the getChildrenByName() 1000 item API limit! + if (siteList.size() > 1000) + { + siteList = siteList.subList(0, 1000); + } + List assocs = this.nodeService.getChildrenByName( + siteRoot, + ContentModel.ASSOC_CONTAINS, + siteList); + List result = new ArrayList(assocs.size()); + for (ChildAssociationRef assoc : assocs) + { + // Ignore any node that is not a "site" type + NodeRef site = assoc.getChildRef(); + QName siteClassName = this.nodeService.getType(site); + if (this.dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE)) + { + result.add(createSiteInfo(site)); + } + } + + return result; + } + + /** + * Creates a site information object given a site node reference + * + * @param siteNodeRef + * site node reference + * @return SiteInfo site information object + */ + private SiteInfo createSiteInfo(NodeRef siteNodeRef) + { + // Get the properties + Map properties = this.nodeService.getProperties(siteNodeRef); + String shortName = (String) properties.get(ContentModel.PROP_NAME); + String sitePreset = (String) properties.get(PROP_SITE_PRESET); + String title = (String) properties.get(ContentModel.PROP_TITLE); + String description = (String) properties.get(ContentModel.PROP_DESCRIPTION); + + // Get the visibility of the site + SiteVisibility visibility = getSiteVisibility(siteNodeRef); + + // Create and return the site information + Map customProperties = getSiteCustomProperties(properties); + SiteInfo siteInfo = new SiteInfoImpl(sitePreset, shortName, title, description, visibility, customProperties, siteNodeRef); + return siteInfo; + } + + /** + * Helper method to get the visibility of the site. If no value is present in the repository then it is calculated from the + * set permissions. This will maintain backwards compatibility with earlier versions of the service implementation. + * + * @param siteNodeRef site node reference + * @return SiteVisibility site visibility + */ + private SiteVisibility getSiteVisibility(NodeRef siteNodeRef) + { + SiteVisibility visibility = SiteVisibility.PRIVATE; + + // Get the visibility value stored in the repo + String visibilityValue = (String)this.nodeService.getProperty(siteNodeRef, SiteModel.PROP_SITE_VISIBILITY); + + // To maintain backwards compatibility calculate the visibility from the permissions + // if there is no value specified on the site node + if (visibilityValue == null) + { + // Examine each permission to see if this is a public site or not + Set permissions = this.permissionService.getAllSetPermissions(siteNodeRef); + for (AccessPermission permission : permissions) + { + if (permission.getAuthority().equals(PermissionService.ALL_AUTHORITIES) == true && + permission.getPermission().equals(SITE_CONSUMER) == true) + { + visibility = SiteVisibility.PUBLIC; + break; + } + } + } + else + { + // Create the enum value from the string + visibility = SiteVisibility.valueOf(visibilityValue); + } + + return visibility; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSite(java.lang.String) + */ + public SiteInfo getSite(final String shortName) + { + // MT share - for activity service system callback + if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + { + final String tenantDomain = tenantService.getDomain(shortName); + final String sName = tenantService.getBaseName(shortName, true); + + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public SiteInfo doWork() throws Exception + { + SiteInfo site = getSiteImpl(sName); + return new SiteInfoImpl(site.getSitePreset(), shortName, site.getTitle(), site.getDescription(), site.getVisibility(), site.getCustomProperties(), site.getNodeRef()); + } + }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + else + { + return getSiteImpl(shortName); + } + } + + private SiteInfo getSiteImpl(String shortName) + { + SiteInfo result = null; + + // Get the site node + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef != null) + { + // Create the site info + result = createSiteInfo(siteNodeRef); + } + + // Return the site information + return result; + } + + /** + * Gets the site's node reference based on its short name + * + * @param shortName short name + * + * @return NodeRef node reference + */ + private NodeRef getSiteNodeRef(String shortName) + { + final String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; + NodeRef siteNodeRef = this.siteNodeRefs.get(cacheKey); + if (siteNodeRef != null) + { + // test for existance - and remove from cache if no longer exists + if (!this.nodeService.exists(siteNodeRef)) + { + this.siteNodeRefs.remove(cacheKey); + siteNodeRef = null; + } + } + else + { + // not in cache - find and store + NodeRef siteRoot = getSiteParent(shortName); + + // the site "short name" directly maps to the cm:name property + siteNodeRef = this.nodeService.getChildByName(siteRoot, ContentModel.ASSOC_CONTAINS, shortName); + + // cache the result if found - null results will be requeried to ensure new sites are found later + if (siteNodeRef != null) + { + this.siteNodeRefs.put(cacheKey, siteNodeRef); + } + } + return siteNodeRef; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#updateSite(org.alfresco.service.cmr.site.SiteInfo) + */ + public void updateSite(SiteInfo siteInfo) + { + NodeRef siteNodeRef = getSiteNodeRef(siteInfo.getShortName()); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_CAN_NOT_UPDATE, new Object[]{siteInfo.getShortName()}); + } + + // Get the sites properties + Map properties = this.nodeService.getProperties(siteNodeRef); + + // Update the properties of the site + // Note: the site preset and short name should never be updated! + properties.put(ContentModel.PROP_TITLE, siteInfo.getTitle()); + properties.put(ContentModel.PROP_DESCRIPTION, siteInfo.getDescription()); + + // Update the isPublic flag + SiteVisibility currentVisibility = getSiteVisibility(siteNodeRef); + SiteVisibility updatedVisibility = siteInfo.getVisibility(); + if (currentVisibility.equals(updatedVisibility) == false) + { + // Remove current visibility permissions + if (SiteVisibility.PUBLIC.equals(currentVisibility) == true) + { + this.permissionService.deletePermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER); + } + else if (SiteVisibility.MODERATED.equals(currentVisibility) == true) + { + this.permissionService.deletePermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES); + // TODO update all child folders ?? ... + } + + // Add new visibility permissions + if (SiteVisibility.PUBLIC.equals(updatedVisibility) == true) + { + this.permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); + } + else if (SiteVisibility.MODERATED.equals(updatedVisibility) == true) + { + this.permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ_PROPERTIES, true); + // TODO update all child folders ?? ... + } + + // Update the site node reference with the updated visibility value + properties.put(SiteModel.PROP_SITE_VISIBILITY, siteInfo.getVisibility()); + } + + // Set the updated properties back onto the site node reference + this.nodeService.setProperties(siteNodeRef, properties); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#deleteSite(java.lang.String) + */ + public void deleteSite(final String shortName) + { + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_CAN_NOT_DELETE, new Object[]{shortName}); + } + + // Delete the cached reference + String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; + this.siteNodeRefs.remove(cacheKey); + + // Delete the node + this.nodeService.deleteNode(siteNodeRef); + + // Delete the associated group's + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + authorityService.deleteAuthority(getSiteGroup(shortName, true)); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#listMembers(java.lang.String, java.lang.String, java.lang.String, int) + */ + public Map listMembers(String shortName, String nameFilter, String roleFilter, int size) + { + return listMembers(shortName, nameFilter, roleFilter, size, false); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#listMembers(String, String, String, int, boolean) + */ + public Map listMembers(String shortName, final String nameFilter, final String roleFilter, final int size, final boolean collapseGroups) + { + // MT share - for activity service system callback + if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + { + final String tenantDomain = tenantService.getDomain(shortName); + final String sName = tenantService.getBaseName(shortName, true); + + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { + public Map doWork() throws Exception + { + return listMembersImpl(sName, nameFilter, roleFilter, size, collapseGroups); + } + }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + else + { + return listMembersImpl(shortName, nameFilter, roleFilter, size, collapseGroups); + } + } + + private Map listMembersImpl(String shortName, String nameFilter, String roleFilter, int size, boolean collapseGroups) + { + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + Map members = new HashMap(32); + + Set permissions = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + for (String permission : permissions) + { + if (roleFilter == null || roleFilter.length() == 0 || roleFilter.equals(permission)) + { + String groupName = getSiteRoleGroup(shortName, permission, true); + Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, groupName, true); + for (String user : users) + { + boolean addUser = true; + if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(user)) + { + // found a filter - does it match person first/last name? + addUser = matchPerson(nameFilter, user); + } + if (addUser) + { + // Add the user and their permission to the returned map + members.put(user, permission); + + // break on max size limit reached + if (members.size() == size) break; + } + } + + Set groups = this.authorityService.getContainedAuthorities(AuthorityType.GROUP, groupName, true); + for (String group : groups) + { + if (collapseGroups == false) + { + if (nameFilter != null && nameFilter.length() != 0) + { + // found a filter - does it match Group name part? + if (group.substring(GROUP_SITE_PREFIX_LENGTH).toLowerCase().contains(nameFilter.toLowerCase())) + { + members.put(group, permission); + } + } + } + else + { + Set subUsers = this.authorityService.getContainedAuthorities(AuthorityType.USER, group, false); + for (String subUser : subUsers) + { + boolean addUser = true; + if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(subUser)) + { + // found a filter - does it match person first/last name? + addUser = matchPerson(nameFilter, subUser); + } + if (addUser) + { + // Add the collapsed user into the members list if they do not already appear in the list + if (members.containsKey(subUser) == false) + { + members.put(subUser, permission); + } + + // break on max size limit reached + if (members.size() == size) break; + } + } + } + } + } + } + + return members; + } + + /** + * Helper to + * @param filter + * @param username + * @return + */ + private boolean matchPerson(String filter, String username) + { + boolean addUser = false; + NodeRef personRef = this.personService.getPerson(username); + Map props = this.nodeService.getProperties(personRef); + String firstName = (String)props.get(ContentModel.PROP_FIRSTNAME); + String lastName = (String)props.get(ContentModel.PROP_LASTNAME); + if (firstName != null && firstName.toLowerCase().indexOf(filter.toLowerCase()) != -1) + { + addUser = true; + } + else if (lastName != null && lastName.toLowerCase().indexOf(filter.toLowerCase()) != -1) + { + addUser = true; + } + return addUser; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getMembersRole(java.lang.String, + * java.lang.String) + */ + public String getMembersRole(String shortName, String authorityName) + { + String result = null; + List roles = getMembersRoles(shortName, authorityName); + if (roles.isEmpty() == false) + { + result = roles.get(0); + } + return result; + } + + public List getMembersRoles(String shortName, String authorityName) + { + List result = new ArrayList(5); + List groups = getPermissionGroups(shortName, authorityName); + for (String group : groups) + { + int index = group.lastIndexOf('_'); + if (index != -1) + { + result.add(group.substring(index + 1)); + } + } + return result; + } + + /** + * Helper method to get the permission groups for a given authority on a site. + * Returns empty List if the user does not have a explicit membership to the site. + * + * @param siteShortName site short name + * @param authorityName authority name + * @return List Permission groups, empty list if no explicit membership set + */ + private List getPermissionGroups(String siteShortName, String authorityName) + { + List result = new ArrayList(5); + Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + for (String role : roles) + { + String roleGroup = getSiteRoleGroup(siteShortName, role, true); + Set authorities = this.authorityService.getContainedAuthorities(null, roleGroup, false); + if (authorities.contains(authorityName) == true) + { + result.add(roleGroup); + } + } + return result; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles() + */ + public List getSiteRoles() + { + Set permissions = permissionService + .getSettablePermissions(SiteModel.TYPE_SITE); + return new ArrayList(permissions); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#isMember(java.lang.String, java.lang.String) + */ + public boolean isMember(String shortName, String authorityName) + { + return (!getPermissionGroups(shortName, authorityName).isEmpty()); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#removeMembership(java.lang.String, java.lang.String) + */ + public void removeMembership(final String shortName, final String authorityName) + { + final NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + // TODO what do we do about the user if they are in a group that has + // rights to the site? + + // Get the current user + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + + // Get the user current role + final String role = getMembersRole(shortName, authorityName); + if (role != null) + { + // Check that we are not about to remove the last site manager + if (SiteModel.SITE_MANAGER.equals(role) == true) + { + Set siteMangers = this.authorityService + .getContainedAuthorities( + AuthorityType.USER, + getSiteRoleGroup(shortName, SITE_MANAGER, true), + true); + if (siteMangers.size() == 1) + { + throw new SiteServiceException(MSG_DO_NOT_REMOVE_MGR, new Object[]{authorityName}); + } + } + + // If ... + // -- the current user has change permissions rights on the site + // or + // -- the user is ourselves + if ((currentUserName.equals(authorityName) == true) || + (permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED)) + { + // Run as system user + AuthenticationUtil.runAs( + new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + // Remove the user from the current permission + // group + String currentGroup = getSiteRoleGroup(shortName, role, true); + authorityService.removeAuthority(currentGroup, authorityName); + + return null; + } + }, AuthenticationUtil.SYSTEM_USER_NAME); + + // Raise events + if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) + { + activityService.postActivity( + ActivityType.SITE_USER_REMOVED, shortName, + ACTIVITY_TOOL, getActivityData(authorityName, "")); + } + else + { + // TODO - update this, if sites support groups + logger.error("setMembership - failed to post activity: unexpected authority type: " + + AuthorityType.getAuthorityType(authorityName)); + } + } + else + { + // Throw an exception + throw new SiteServiceException(MSG_CAN_NOT_REMOVE_MSHIP, new Object[]{shortName}); + } + } + else + { + // Throw an exception + throw new SiteServiceException(MSG_CAN_NOT_REMOVE_MSHIP, new Object[]{shortName}); + } + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#setMembership(java.lang.String, + * java.lang.String, java.lang.String) + */ + public void setMembership(final String shortName, + final String authorityName, + final String role) + { + final NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + // Get the user's current role + final String currentRole = getMembersRole(shortName, authorityName); + + // Do nothing if the role of the user is not being changed + if (currentRole == null || role.equals(currentRole) == false) + { + // TODO if this is the only site manager do not down grade their + // permissions + + // Get the visibility of the site + SiteVisibility visibility = getSiteVisibility(siteNodeRef); + + // If we are ... + // -- the current user has change permissions rights on the site + // or we are ... + // -- referring to a public site and + // -- the role being set is consumer and + // -- the user being added is ourselves and + // -- the member does not already have permissions + // ... then we can set the permissions as system user + final String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + if ((permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) || + (SiteVisibility.PUBLIC.equals(visibility) == true && + role.equals(SiteModel.SITE_CONSUMER) == true && + authorityName.equals(currentUserName) == true && + currentRole == null)) + { + // Check that we are not about to remove the last site manager + if (SiteModel.SITE_MANAGER.equals(currentRole) == true) + { + Set siteMangers = this.authorityService.getContainedAuthorities(AuthorityType.USER, + getSiteRoleGroup(shortName, SITE_MANAGER, true), + true); + if (siteMangers.size() == 1) + { + throw new SiteServiceException(MSG_DO_NOT_CHANGE_MGR, new Object[]{authorityName}); + } + } + + // Run as system user + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + if (currentRole != null) + { + // Remove the user from the current + // permission group + String currentGroup = getSiteRoleGroup(shortName, currentRole, true); + authorityService.removeAuthority(currentGroup, authorityName); + } + + // Add the user to the new permission group + String newGroup = getSiteRoleGroup(shortName, role, true); + authorityService.addAuthority(newGroup, authorityName); + + return null; + } + + }, AuthenticationUtil.SYSTEM_USER_NAME); + + if (currentRole == null) + { + if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) + { + activityService.postActivity( + ActivityType.SITE_USER_JOINED, shortName, + ACTIVITY_TOOL, getActivityData(authorityName, role)); + } + else + { + // TODO - update this, if sites support groups + logger + .error("setMembership - failed to post activity: unexpected authority type: " + + AuthorityType.getAuthorityType(authorityName)); + } + } + else + { + if (AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER) + { + activityService.postActivity( + ActivityType.SITE_USER_ROLE_UPDATE, shortName, + ACTIVITY_TOOL, getActivityData(authorityName, role)); + } + else + { + // TODO - update this, if sites support groups + logger.error("setMembership - failed to post activity: unexpected authority type: " + + AuthorityType.getAuthorityType(authorityName)); + } + } + } + else + { + // Raise a permission exception + throw new SiteServiceException(MSG_CAN_NOT_CHANGE_MSHIP, new Object[]{shortName}); + } + } + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#createContainer(java.lang.String, + * java.lang.String, org.alfresco.service.namespace.QName, + * java.util.Map) + */ + public NodeRef createContainer(String shortName, + String componentId, + QName containerType, + Map containerProperties) + { + // Check for the component id + ParameterCheck.mandatoryString("componentId", componentId); + + // retrieve site + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + // retrieve component folder within site + NodeRef containerNodeRef = null; + try + { + containerNodeRef = findContainer(siteNodeRef, componentId); + } + catch (FileNotFoundException e) + { + } + + // create the container node reference + if (containerNodeRef == null) + { + if (containerType == null) + { + containerType = ContentModel.TYPE_FOLDER; + } + + // create component folder + FileInfo fileInfo = fileFolderService.create(siteNodeRef, + componentId, containerType); + + // Get the created container + containerNodeRef = fileInfo.getNodeRef(); + + // Set the properties if they have been provided + if (containerProperties != null) + { + Map props = this.nodeService + .getProperties(containerNodeRef); + props.putAll(containerProperties); + this.nodeService.setProperties(containerNodeRef, props); + } + + // Add the container aspect + Map aspectProps = new HashMap( + 1); + aspectProps.put(SiteModel.PROP_COMPONENT_ID, componentId); + this.nodeService.addAspect(containerNodeRef, ASPECT_SITE_CONTAINER, + aspectProps); + + // Make the container a tag scope + this.taggingService.addTagScope(containerNodeRef); + } + + return containerNodeRef; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getContainer(java.lang.String) + */ + public NodeRef getContainer(String shortName, String componentId) + { + ParameterCheck.mandatoryString("componentId", componentId); + + // retrieve site + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + // retrieve component folder within site + // NOTE: component id is used for folder name + NodeRef containerNodeRef = null; + try + { + containerNodeRef = findContainer(siteNodeRef, componentId); + } + catch (FileNotFoundException e) + { + } + + return containerNodeRef; + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#hasContainer(java.lang.String) + */ + public boolean hasContainer(String shortName, String componentId) + { + ParameterCheck.mandatoryString("componentId", componentId); + + // retrieve site + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[]{shortName}); + } + + // retrieve component folder within site + // NOTE: component id is used for folder name + boolean hasContainer = false; + try + { + findContainer(siteNodeRef, componentId); + hasContainer = true; + } + catch (FileNotFoundException e) + { + } + + return hasContainer; + } + + /** + * Locate site "container" folder for component + * + * @param siteNodeRef + * site + * @param componentId + * component id + * @return "container" node ref, if it exists + * @throws FileNotFoundException + */ + private NodeRef findContainer(NodeRef siteNodeRef, String componentId) + throws FileNotFoundException + { + List paths = new ArrayList(1); + paths.add(componentId); + FileInfo fileInfo = fileFolderService.resolveNamePath(siteNodeRef, + paths); + if (!fileInfo.isFolder()) + { + throw new SiteServiceException(MSG_SITE_CONTAINER_NOT_FOLDER, new Object[]{fileInfo.getName()}); + } + return fileInfo.getNodeRef(); + } + + /** + * Helper method to get the activity data for a user + * + * @param userName user name + * @param role role + * @return + */ + private String getActivityData(String userName, String role) + { + String memberFN = ""; + String memberLN = ""; + NodeRef person = personService.getPerson(userName); + if (person != null) + { + memberFN = (String) nodeService.getProperty(person, + ContentModel.PROP_FIRSTNAME); + memberLN = (String) nodeService.getProperty(person, + ContentModel.PROP_LASTNAME); + } + + try + { + JSONObject activityData = new JSONObject(); + activityData.put("role", role); + activityData.put("memberUserName", userName); + activityData.put("memberFirstName", memberFN); + activityData.put("memberLastName", memberLN); + activityData.put("title", (memberFN + " " + memberLN + " (" + + userName + ")").trim()); + return activityData.toString(); + } catch (JSONException je) + { + // log error, subsume exception + logger.error("Failed to get activity data: " + je); + return ""; + } + } +} diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 1b23041ff4..4e0a7b061b 100755 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -1,1254 +1,1254 @@ -/* - * Copyright (C) 2005-2009 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.repo.tenant; - -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.transaction.UserTransaction; - -import net.sf.acegisecurity.providers.encoding.PasswordEncoder; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.admin.RepoModelDefinition; -import org.alfresco.repo.attributes.BooleanAttributeValue; -import org.alfresco.repo.attributes.MapAttribute; -import org.alfresco.repo.attributes.MapAttributeValue; -import org.alfresco.repo.attributes.StringAttributeValue; -import org.alfresco.repo.content.TenantRoutingFileContentStore; -import org.alfresco.repo.dictionary.DictionaryComponent; -import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.node.db.DbNodeServiceImpl; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.site.SiteAVMBootstrap; -import org.alfresco.repo.usage.UserUsageBootstrapJob; -import org.alfresco.repo.usage.UserUsageTrackingComponent; -import org.alfresco.repo.workflow.WorkflowDeployer; -import org.alfresco.service.cmr.admin.RepoAdminService; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.module.ModuleService; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.view.RepositoryExporterService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * MT Admin Service Implementation. - * - */ - -public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationContextAware -{ - // Logger - private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); - - // Keep hold of the app context - private ApplicationContext ctx; - - // Dependencies - private NodeService nodeService; - private DictionaryComponent dictionaryComponent; - private RepoAdminService repoAdminService; - private AuthenticationComponent authenticationComponent; - private TransactionService transactionService; - private MultiTServiceImpl tenantService; - private AttributeService attributeService; - private PasswordEncoder passwordEncoder; - private TenantRoutingFileContentStore tenantFileContentStore; - private WorkflowService workflowService; - private RepositoryExporterService repositoryExporterService; - private ModuleService moduleService; - private SiteAVMBootstrap siteAVMBootstrap; - private List workflowDeployers = new ArrayList(); - - private String baseAdminUsername = null; - - /* - * Tenant domain/ids are unique strings that are case-insensitive. Tenant ids must be valid filenames. - * They may also map onto domains and hence should allow valid FQDN. - * - * The following PCRE-style - * regex defines a valid label within a FQDN: - * - * ^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$ - * - * Less formally: - * - * o Case insensitive - * o First/last character: alphanumeric - * o Interior characters: alphanumeric plus hyphen - * o Minimum length: 2 characters - * o Maximum length: 63 characters - * - * The FQDN (fully qualified domain name) has the following constraints: - * - * o Maximum 255 characters (***) - * o Must contain at least one alpha - * - * Note: (***) Due to various internal restrictions (such as store identifier) we restrict tenant ids to 75 characters. - */ - - protected final static String REGEX_VALID_DNS_LABEL = "^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"; - - protected final static String REGEX_CONTAINS_ALPHA = "^(.*)[a-zA-Z](.*)$"; - - protected final static int MAX_LEN = 75; - - public void setNodeService(DbNodeServiceImpl dbNodeService) - { - this.nodeService = dbNodeService; - } - - public void setDictionaryComponent(DictionaryComponent dictionaryComponent) - { - this.dictionaryComponent = dictionaryComponent; - } - - public void setRepoAdminService(RepoAdminService repoAdminService) - { - this.repoAdminService = repoAdminService; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setTenantService(MultiTServiceImpl tenantService) - { - this.tenantService = tenantService; - } - - public void setAttributeService(AttributeService attributeService) - { - this.attributeService = attributeService; - } - - public void setPasswordEncoder(PasswordEncoder passwordEncoder) - { - this.passwordEncoder = passwordEncoder; - } - - public void setTenantFileContentStore(TenantRoutingFileContentStore tenantFileContentStore) - { - this.tenantFileContentStore = tenantFileContentStore; - } - - public void setWorkflowService(WorkflowService workflowService) - { - this.workflowService = workflowService; - } - - public void setRepositoryExporterService(RepositoryExporterService repositoryExporterService) - { - this.repositoryExporterService = repositoryExporterService; - } - - /** - * @deprecated see setWorkflowDeployers - */ - public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) - { - // NOOP - logger.warn(WARN_MSG); - } - - public void setModuleService(ModuleService moduleService) - { - this.moduleService = moduleService; - } - - public void setSiteAVMBootstrap(SiteAVMBootstrap siteAVMBootstrap) - { - this.siteAVMBootstrap = siteAVMBootstrap; - } - - public void setBaseAdminUsername(String baseAdminUsername) - { - this.baseAdminUsername = baseAdminUsername; - } - - public static final String PROTOCOL_STORE_USER = "user"; - public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; - public static final String PROTOCOL_STORE_SYSTEM = "system"; - public static final String PROTOCOL_STORE_ARCHIVE = "archive"; - public static final String STORE_BASE_ID_USER = "alfrescoUserStore"; - public static final String STORE_BASE_ID_SYSTEM = "system"; - public static final String STORE_BASE_ID_VERSION1 = "lightWeightVersionStore"; // deprecated - public static final String STORE_BASE_ID_VERSION2 = "version2Store"; - public static final String STORE_BASE_ID_SPACES = "SpacesStore"; - - - private static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; - private static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; - private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; - - private List tenantDeployers = new ArrayList(); - - private static final String WARN_MSG = "Please update your alfresco/extension/mt/mt-admin-context.xml to use baseMultiTAdminService (see latest alfresco/extension/mt/mt-admin-context.xml.sample)"; - - protected void checkProperties() - { - if (moduleService == null || siteAVMBootstrap == null || baseAdminUsername == null) - { - logger.warn(WARN_MSG); - } - PropertyCheck.mandatory(this, "NodeService", nodeService); - PropertyCheck.mandatory(this, "DictionaryComponent", dictionaryComponent); - PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); - PropertyCheck.mandatory(this, "TransactionService", transactionService); - PropertyCheck.mandatory(this, "TenantService", tenantService); - PropertyCheck.mandatory(this, "AttributeService", attributeService); - PropertyCheck.mandatory(this, "PasswordEncoder", passwordEncoder); - PropertyCheck.mandatory(this, "TenantFileContentStore", tenantFileContentStore); - PropertyCheck.mandatory(this, "WorkflowService", workflowService); - PropertyCheck.mandatory(this, "RepositoryExporterService", repositoryExporterService); - PropertyCheck.mandatory(this, "siteAVMBootstrap", siteAVMBootstrap); - PropertyCheck.mandatory(this, "moduleService", moduleService); - PropertyCheck.mandatory(this, "baseAdminUsername", baseAdminUsername); - } - - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.ctx = applicationContext; - } - - public void startTenants() - { - checkProperties(); - - AuthenticationUtil.setMtEnabled(true); - - // initialise the tenant admin service and status of tenants (using attribute service) - // note: this requires that the repository schema has already been initialised - - // register dictionary - to allow enable/disable tenant callbacks - register(dictionaryComponent); - - // register file store - to allow enable/disable tenant callbacks - register(tenantFileContentStore); - - UserTransaction userTransaction = transactionService.getUserTransaction(); - - try - { - authenticationComponent.setSystemUserAsCurrentUser(); - userTransaction.begin(); - - // bootstrap Tenant Service internal cache - List tenants = getAllTenants(); - - int enabledCount = 0; - int disabledCount = 0; - - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - // this will also call tenant deployers registered so far ... - enableTenant(tenant.getTenantDomain(), true); - enabledCount++; - } - else - { - // explicitly disable, without calling disableTenant callback - disableTenant(tenant.getTenantDomain(), false); - disabledCount++; - } - } - - tenantService.register(this); // callback to refresh tenantStatus cache - - userTransaction.commit(); - - if (logger.isInfoEnabled()) - { - logger.info(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", - enabledCount, disabledCount)); - } - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to bootstrap tenants", e); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - } - - public void stopTenants() - { - tenantDeployers.clear(); - tenantDeployers = null; - } - - /** - * @see TenantAdminService.createTenant() - */ - public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword) - { - createTenant(tenantDomain, tenantAdminRawPassword, null); - } - - /** - * @see TenantAdminService.createTenant() - */ - public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) - { - ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); - - initTenant(tenantDomain, rootContentStoreDir); - - try - { - // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); - - dictionaryComponent.init(); - tenantFileContentStore.init(); - - // create tenant-specific stores - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); - - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - - // deprecated - ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); - bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); - - ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); - bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); - bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - - siteAVMBootstrap.bootstrap(); - - // notify listeners that tenant has been created & hence enabled - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) - { - workflowDeployer.init(); - } - - // bootstrap modules (if any) - moduleService.startModules(); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - - logger.info("Tenant created: " + tenantDomain); - } - - /** - * Export tenant - equivalent to the tenant admin running a 'complete repo' export from the Web Client Admin - */ - public void exportTenant(final String tenantDomain, final File directoryDestination) - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - repositoryExporterService.export(directoryDestination, tenantDomain); - return null; - } - }, getSystemUser(tenantDomain)); - - logger.info("Tenant exported: " + tenantDomain); - } - - /** - * Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml. - */ - public void importTenant(final String tenantDomain, final File directorySource, String rootContentStoreDir) - { - initTenant(tenantDomain, rootContentStoreDir); - - try - { - // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); - - dictionaryComponent.init(); - tenantFileContentStore.init(); - - // import tenant-specific stores - importBootstrapUserTenantStore(tenantDomain, directorySource); - importBootstrapSystemTenantStore(tenantDomain, directorySource); - importBootstrapVersionTenantStore(tenantDomain, directorySource); - importBootstrapSpacesArchiveTenantStore(tenantDomain, directorySource); - importBootstrapSpacesModelsTenantStore(tenantDomain, directorySource); - importBootstrapSpacesTenantStore(tenantDomain, directorySource); - - // notify listeners that tenant has been created & hence enabled - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) - { - workflowDeployer.init(); - } - - // bootstrap modules (if any) - moduleService.startModules(); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - - logger.info("Tenant imported: " + tenantDomain); - } - - public boolean existsTenant(String tenantDomain) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - return (getTenantAttributes(tenantDomain) != null); - } - - private void putTenantAttributes(String tenantDomain, Tenant tenant) - { - if (! attributeService.exists(TENANTS_ATTRIBUTE_PATH)) - { - // bootstrap - attributeService.setAttribute("", TENANTS_ATTRIBUTE_PATH, new MapAttributeValue()); - } - - MapAttribute tenantProps = new MapAttributeValue(); - tenantProps.put(TENANT_ATTRIBUTE_ENABLED, new BooleanAttributeValue(tenant.isEnabled())); - tenantProps.put(TENANT_ROOT_CONTENT_STORE_DIR, new StringAttributeValue(tenant.getRootContentStoreDir())); - - attributeService.setAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain, tenantProps); - - // update tenant status cache - ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); - } - - private Tenant getTenantAttributes(String tenantDomain) - { - if (attributeService.exists(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain)) - { - MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain); - if (map != null) - { - return new Tenant(tenantDomain, - map.get(TENANT_ATTRIBUTE_ENABLED).getBooleanValue(), - map.get(TENANT_ROOT_CONTENT_STORE_DIR).getStringValue()); - } - } - - return null; - } - - public void enableTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - if (isEnabledTenant(tenantDomain)) - { - logger.warn("Tenant already enabled: " + tenantDomain); - } - - enableTenant(tenantDomain, true); - } - - private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, true, tenant.getRootContentStoreDir()); // enable - putTenantAttributes(tenantDomain, tenant); - - if (notifyTenantDeployers) - { - // notify listeners that tenant has been enabled - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - } - - logger.info("Tenant enabled: " + tenantDomain); - } - - public void disableTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - if (! isEnabledTenant(tenantDomain)) - { - logger.warn("Tenant already disabled: " + tenantDomain); - } - - disableTenant(tenantDomain, true); - } - - public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - if (notifyTenantDeployers) - { - // notify listeners that tenant has been disabled - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - } - - // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, false, tenant.getRootContentStoreDir()); // disable - putTenantAttributes(tenantDomain, tenant); - - logger.info("Tenant disabled: " + tenantDomain); - } - - public boolean isEnabledTenant(String tenantDomain) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - Tenant tenant = getTenantAttributes(tenantDomain); - if (tenant != null) - { - return tenant.isEnabled(); - } - - return false; - } - - protected String getRootContentStoreDir(String tenantDomain) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - Tenant tenant = getTenantAttributes(tenantDomain); - if (tenant != null) - { - return tenant.getRootContentStoreDir(); - } - - return null; - } - - protected void putRootContentStoreDir(String tenantDomain, String rootContentStoreDir) - { - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, tenant.isEnabled(), rootContentStoreDir); - putTenantAttributes(tenantDomain, tenant); - } - - public Tenant getTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); - } - - /** - * @see TenantAdminService.deleteTenant() - */ - public void deleteTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - else - { - try - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - List workflowDefs = workflowService.getDefinitions(); - if (workflowDefs != null) - { - for (WorkflowDefinition workflowDef : workflowDefs) - { - workflowService.undeployDefinition(workflowDef.getId()); - } - } - - List messageResourceBundles = repoAdminService.getMessageBundles(); - if (messageResourceBundles != null) - { - for (String messageResourceBundle : messageResourceBundles) - { - repoAdminService.undeployMessageBundle(messageResourceBundle); - } - } - - List models = repoAdminService.getModels(); - if (models != null) - { - for (RepoModelDefinition model : models) - { - repoAdminService.undeployModel(model.getRepoName()); - } - } - - return null; - } - }, getSystemUser(tenantDomain)); - - final String tenantAdminUser = getTenantAdminUser(tenantDomain); - - // delete tenant-specific stores - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER))); - - - // notify listeners that tenant has been deleted & hence disabled - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - - // remove tenant - attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); - } - catch (Throwable t) - { - throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); - } - } - } - - /** - * @see TenantAdminService.getAllTenants() - */ - public List getAllTenants() - { - MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH); - - List tenants = new ArrayList(); - - if (map != null) - { - // note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache - Set tenantDomains = map.keySet(); - - for (String tenantDomain : tenantDomains) - { - Tenant tenant = getTenantAttributes(tenantDomain); - tenants.add(new Tenant(tenantDomain, tenant.isEnabled(), tenant.getRootContentStoreDir())); - } - } - - return tenants; // list of tenants or empty list - } - - private void importBootstrapSystemTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Version Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_system.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap"); - systemImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - } - - private void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific System Store - StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); - StoreRef tenantBootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - systemImporterBootstrap.setStoreUrl(tenantBootstrapStoreRef.toString()); - - // override default property (workspace://SpacesStore) - List mustNotExistStoreUrls = new ArrayList(); - mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)).toString()); - systemImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); - - systemImporterBootstrap.bootstrap(); - - // reset since systemImporter is singleton (hence reused) - systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(tenantBootstrapStoreRef)); - } - - private void importBootstrapUserTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific User Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_users.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); - userImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); - } - - private void bootstrapUserTenantStore(ImporterBootstrap userImporterBootstrap, String tenantDomain, char[] tenantAdminRawPassword) - { - // Bootstrap Tenant-Specific User Store - StoreRef bootstrapStoreRef = userImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - userImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - // override admin username property - Properties props = userImporterBootstrap.getConfiguration(); - props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); - - if (tenantAdminRawPassword != null) - { - String salt = null; // GUID.generate(); - props.put("alfresco_user_store.adminpassword", passwordEncoder.encodePassword(new String(tenantAdminRawPassword), salt)); - } - - userImporterBootstrap.bootstrap(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - private void importBootstrapVersionTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Version Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_versions2.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap"); - versionImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); - } - - private void bootstrapVersionTenantStore(ImporterBootstrap versionImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific Version Store - StoreRef bootstrapStoreRef = versionImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - versionImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - versionImporterBootstrap.bootstrap(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - private void importBootstrapSpacesArchiveTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Archive Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces_archive.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap"); - spacesArchiveImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); - } - - private void bootstrapSpacesArchiveTenantStore(ImporterBootstrap spacesArchiveImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific Spaces Archive Store - StoreRef bootstrapStoreRef = spacesArchiveImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - spacesArchiveImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - // override default property (archive://SpacesStore) - List mustNotExistStoreUrls = new ArrayList(); - mustNotExistStoreUrls.add(bootstrapStoreRef.toString()); - spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); - - spacesArchiveImporterBootstrap.bootstrap(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - private void importBootstrapSpacesModelsTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_models.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); - spacesImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - } - - private void importBootstrapSpacesTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces.acp"); - bootstrapView.put("uuidBinding", "UPDATE_EXISTING"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); - spacesImporterBootstrap.setBootstrapViews(bootstrapViews); - - spacesImporterBootstrap.setUseExistingStore(true); - - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - } - - private void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific Spaces Store - StoreRef bootstrapStoreRef = spacesImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - spacesImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - // override admin username property - Properties props = spacesImporterBootstrap.getConfiguration(); - props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); - - // override guest username property - props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); - - spacesImporterBootstrap.bootstrap(); - - // calculate any missing usages - UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)ctx.getBean(UserUsageBootstrapJob.KEY_COMPONENT); - userUsageTrackingComponent.bootstrapInternal(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - public void deployTenants(final TenantDeployer deployer, Log logger) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("Deployer must be provided"); - } - if (logger == null) - { - throw new AlfrescoRuntimeException("Logger must be provided"); - } - - if (tenantService.isEnabled()) - { - UserTransaction userTransaction = transactionService.getUserTransaction(); - authenticationComponent.setSystemUserAsCurrentUser(); - - List tenants = null; - try - { - userTransaction.begin(); - tenants = getAllTenants(); - userTransaction.commit(); - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to get tenants", e); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - try - { - // deploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - // init the service within tenant context - deployer.init(); - return null; - } - }, getSystemUser(tenant.getTenantDomain())); - - } - catch (Throwable e) - { - logger.error("Deployment failed" + e); - - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - logger.error(stringWriter.toString()); - - // tenant deploy failure should not necessarily affect other tenants - } - } - } - } - } - - public void undeployTenants(final TenantDeployer deployer, Log logger) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("Deployer must be provided"); - } - if (logger == null) - { - throw new AlfrescoRuntimeException("Logger must be provided"); - } - - if (tenantService.isEnabled()) - { - UserTransaction userTransaction = transactionService.getUserTransaction(); - authenticationComponent.setSystemUserAsCurrentUser(); - - List tenants = null; - try - { - userTransaction.begin(); - tenants = getAllTenants(); - userTransaction.commit(); - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to get tenants", e); - } - - try - { - AuthenticationUtil.pushAuthentication(); - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - try - { - // undeploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - // destroy the service within tenant context - deployer.destroy(); - return null; - } - }, getSystemUser(tenant.getTenantDomain())); - - } - catch (Throwable e) - { - logger.error("Undeployment failed" + e); - - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - logger.error(stringWriter.toString()); - - // tenant undeploy failure should not necessarily affect other tenants - } - } - } - } - finally - { - AuthenticationUtil.popAuthentication(); - } - } - } - - public void register(TenantDeployer deployer) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("TenantDeployer must be provided"); - } - - if (! tenantDeployers.contains(deployer)) - { - tenantDeployers.add(deployer); - } - } - - public void unregister(TenantDeployer deployer) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("TenantDeployer must be provided"); - } - - if (tenantDeployers != null) - { - tenantDeployers.remove(deployer); - } - } - - public void register(WorkflowDeployer workflowDeployer) - { - if (workflowDeployer == null) - { - throw new AlfrescoRuntimeException("WorkflowDeployer must be provided"); - } - - if (! workflowDeployers.contains(workflowDeployer)) - { - workflowDeployers.add(workflowDeployer); - } - } - - public void resetCache(String tenantDomain) - { - if (existsTenant(tenantDomain)) - { - if (isEnabledTenant(tenantDomain)) - { - enableTenant(tenantDomain); - } - else - { - disableTenant(tenantDomain); - } - } - else - { - throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); - } - } - - private void initTenant(String tenantDomain, String rootContentStoreDir) - { - validateTenantName(tenantDomain); - - if (existsTenant(tenantDomain)) - { - throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); - } - - if (rootContentStoreDir == null) - { - rootContentStoreDir = tenantFileContentStore.getDefaultRootDir(); - } - else - { - File tenantRootDir = new File(rootContentStoreDir); - if ((tenantRootDir.exists()) && (tenantRootDir.list().length != 0)) - { - throw new AlfrescoRuntimeException("Tenant root directory is not empty: " + rootContentStoreDir); - } - } - - // init - need to enable tenant (including tenant service) before stores bootstrap - Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); - putTenantAttributes(tenantDomain, tenant); - } - - private void validateTenantName(String tenantDomain) - { - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - if (tenantDomain.length() > MAX_LEN) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must be less than " + MAX_LEN + " characters)"); - } - - if (! Pattern.matches(REGEX_CONTAINS_ALPHA, tenantDomain)) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must contain at least one alpha character)"); - } - - String[] dnsLabels = tenantDomain.split("\\."); - if (dnsLabels.length != 0) - { - for (int i = 0; i < dnsLabels.length; i++) - { - if (! Pattern.matches(REGEX_VALID_DNS_LABEL, dnsLabels[i])) - { - throw new IllegalArgumentException(dnsLabels[i] + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); - } - } - } - else - { - if (! Pattern.matches(REGEX_VALID_DNS_LABEL, tenantDomain)) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); - } - } - } - - // tenant deployer/user services delegated to tenant service - - public boolean isEnabled() - { - return tenantService.isEnabled(); - } - - public String getCurrentUserDomain() - { - return tenantService.getCurrentUserDomain(); - } - - public String getUserDomain(String username) - { - return tenantService.getUserDomain(username); - } - - public String getBaseNameUser(String username) - { - return tenantService.getBaseNameUser(username); - } - - public String getDomainUser(String baseUsername, String tenantDomain) - { - return tenantService.getDomainUser(baseUsername, tenantDomain); - } - - public String getDomain(String name) - { - return tenantService.getDomain(name); - } - - // local helpers - - public String getBaseAdminUser() - { - // default for backwards compatibility only - eg. upgrade of existing MT instance (mt-admin-context.xml.sample) - if (baseAdminUsername != null) - { - return baseAdminUsername; - } - return AuthenticationUtil.getAdminUserName(); - } - - private String getSystemUser(String tenantDomain) - { - return tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); - } - - private String getTenantAdminUser(String tenantDomain) - { - - return tenantService.getDomainUser(getBaseAdminUser(), tenantDomain); - } - - private String getTenantGuestUser(String tenantDomain) - { - return tenantService.getDomainUser(authenticationComponent.getGuestUserName(), tenantDomain); - } -} +/* + * Copyright (C) 2005-2009 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.repo.tenant; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.transaction.UserTransaction; + +import net.sf.acegisecurity.providers.encoding.PasswordEncoder; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.RepoModelDefinition; +import org.alfresco.repo.attributes.BooleanAttributeValue; +import org.alfresco.repo.attributes.MapAttribute; +import org.alfresco.repo.attributes.MapAttributeValue; +import org.alfresco.repo.attributes.StringAttributeValue; +import org.alfresco.repo.content.TenantRoutingFileContentStore; +import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.node.db.DbNodeServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.site.SiteAVMBootstrap; +import org.alfresco.repo.usage.UserUsageBootstrapJob; +import org.alfresco.repo.usage.UserUsageTrackingComponent; +import org.alfresco.repo.workflow.WorkflowDeployer; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.view.RepositoryExporterService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * MT Admin Service Implementation. + * + */ + +public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationContextAware +{ + // Logger + private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); + + // Keep hold of the app context + private ApplicationContext ctx; + + // Dependencies + private NodeService nodeService; + private DictionaryComponent dictionaryComponent; + private RepoAdminService repoAdminService; + private AuthenticationContext authenticationContext; + private TransactionService transactionService; + private MultiTServiceImpl tenantService; + private AttributeService attributeService; + private PasswordEncoder passwordEncoder; + private TenantRoutingFileContentStore tenantFileContentStore; + private WorkflowService workflowService; + private RepositoryExporterService repositoryExporterService; + private ModuleService moduleService; + private SiteAVMBootstrap siteAVMBootstrap; + private List workflowDeployers = new ArrayList(); + + private String baseAdminUsername = null; + + /* + * Tenant domain/ids are unique strings that are case-insensitive. Tenant ids must be valid filenames. + * They may also map onto domains and hence should allow valid FQDN. + * + * The following PCRE-style + * regex defines a valid label within a FQDN: + * + * ^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$ + * + * Less formally: + * + * o Case insensitive + * o First/last character: alphanumeric + * o Interior characters: alphanumeric plus hyphen + * o Minimum length: 2 characters + * o Maximum length: 63 characters + * + * The FQDN (fully qualified domain name) has the following constraints: + * + * o Maximum 255 characters (***) + * o Must contain at least one alpha + * + * Note: (***) Due to various internal restrictions (such as store identifier) we restrict tenant ids to 75 characters. + */ + + protected final static String REGEX_VALID_DNS_LABEL = "^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"; + + protected final static String REGEX_CONTAINS_ALPHA = "^(.*)[a-zA-Z](.*)$"; + + protected final static int MAX_LEN = 75; + + public void setNodeService(DbNodeServiceImpl dbNodeService) + { + this.nodeService = dbNodeService; + } + + public void setDictionaryComponent(DictionaryComponent dictionaryComponent) + { + this.dictionaryComponent = dictionaryComponent; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setTenantService(MultiTServiceImpl tenantService) + { + this.tenantService = tenantService; + } + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) + { + this.passwordEncoder = passwordEncoder; + } + + public void setTenantFileContentStore(TenantRoutingFileContentStore tenantFileContentStore) + { + this.tenantFileContentStore = tenantFileContentStore; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public void setRepositoryExporterService(RepositoryExporterService repositoryExporterService) + { + this.repositoryExporterService = repositoryExporterService; + } + + /** + * @deprecated see setWorkflowDeployers + */ + public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) + { + // NOOP + logger.warn(WARN_MSG); + } + + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + public void setSiteAVMBootstrap(SiteAVMBootstrap siteAVMBootstrap) + { + this.siteAVMBootstrap = siteAVMBootstrap; + } + + public void setBaseAdminUsername(String baseAdminUsername) + { + this.baseAdminUsername = baseAdminUsername; + } + + public static final String PROTOCOL_STORE_USER = "user"; + public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; + public static final String PROTOCOL_STORE_SYSTEM = "system"; + public static final String PROTOCOL_STORE_ARCHIVE = "archive"; + public static final String STORE_BASE_ID_USER = "alfrescoUserStore"; + public static final String STORE_BASE_ID_SYSTEM = "system"; + public static final String STORE_BASE_ID_VERSION1 = "lightWeightVersionStore"; // deprecated + public static final String STORE_BASE_ID_VERSION2 = "version2Store"; + public static final String STORE_BASE_ID_SPACES = "SpacesStore"; + + + private static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; + private static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; + private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; + + private List tenantDeployers = new ArrayList(); + + private static final String WARN_MSG = "Please update your alfresco/extension/mt/mt-admin-context.xml to use baseMultiTAdminService (see latest alfresco/extension/mt/mt-admin-context.xml.sample)"; + + protected void checkProperties() + { + if (moduleService == null || siteAVMBootstrap == null || baseAdminUsername == null) + { + logger.warn(WARN_MSG); + } + PropertyCheck.mandatory(this, "NodeService", nodeService); + PropertyCheck.mandatory(this, "DictionaryComponent", dictionaryComponent); + PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); + PropertyCheck.mandatory(this, "TransactionService", transactionService); + PropertyCheck.mandatory(this, "TenantService", tenantService); + PropertyCheck.mandatory(this, "AttributeService", attributeService); + PropertyCheck.mandatory(this, "PasswordEncoder", passwordEncoder); + PropertyCheck.mandatory(this, "TenantFileContentStore", tenantFileContentStore); + PropertyCheck.mandatory(this, "WorkflowService", workflowService); + PropertyCheck.mandatory(this, "RepositoryExporterService", repositoryExporterService); + PropertyCheck.mandatory(this, "siteAVMBootstrap", siteAVMBootstrap); + PropertyCheck.mandatory(this, "moduleService", moduleService); + PropertyCheck.mandatory(this, "baseAdminUsername", baseAdminUsername); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.ctx = applicationContext; + } + + public void startTenants() + { + checkProperties(); + + AuthenticationUtil.setMtEnabled(true); + + // initialise the tenant admin service and status of tenants (using attribute service) + // note: this requires that the repository schema has already been initialised + + // register dictionary - to allow enable/disable tenant callbacks + register(dictionaryComponent); + + // register file store - to allow enable/disable tenant callbacks + register(tenantFileContentStore); + + UserTransaction userTransaction = transactionService.getUserTransaction(); + + try + { + authenticationContext.setSystemUserAsCurrentUser(); + userTransaction.begin(); + + // bootstrap Tenant Service internal cache + List tenants = getAllTenants(); + + int enabledCount = 0; + int disabledCount = 0; + + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + // this will also call tenant deployers registered so far ... + enableTenant(tenant.getTenantDomain(), true); + enabledCount++; + } + else + { + // explicitly disable, without calling disableTenant callback + disableTenant(tenant.getTenantDomain(), false); + disabledCount++; + } + } + + tenantService.register(this); // callback to refresh tenantStatus cache + + userTransaction.commit(); + + if (logger.isInfoEnabled()) + { + logger.info(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", + enabledCount, disabledCount)); + } + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to bootstrap tenants", e); + } + finally + { + authenticationContext.clearCurrentSecurityContext(); + } + } + + public void stopTenants() + { + tenantDeployers.clear(); + tenantDeployers = null; + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword) + { + createTenant(tenantDomain, tenantAdminRawPassword, null); + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) + { + ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); + + initTenant(tenantDomain, rootContentStoreDir); + + try + { + // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); + + dictionaryComponent.init(); + tenantFileContentStore.init(); + + // create tenant-specific stores + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); + bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); + + // deprecated + ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); + bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); + + ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); + bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); + bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + + siteAVMBootstrap.bootstrap(); + + // notify listeners that tenant has been created & hence enabled + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + + // bootstrap workflows + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } + + // bootstrap modules (if any) + moduleService.startModules(); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + + logger.info("Tenant created: " + tenantDomain); + } + + /** + * Export tenant - equivalent to the tenant admin running a 'complete repo' export from the Web Client Admin + */ + public void exportTenant(final String tenantDomain, final File directoryDestination) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + repositoryExporterService.export(directoryDestination, tenantDomain); + return null; + } + }, getSystemUser(tenantDomain)); + + logger.info("Tenant exported: " + tenantDomain); + } + + /** + * Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml. + */ + public void importTenant(final String tenantDomain, final File directorySource, String rootContentStoreDir) + { + initTenant(tenantDomain, rootContentStoreDir); + + try + { + // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); + + dictionaryComponent.init(); + tenantFileContentStore.init(); + + // import tenant-specific stores + importBootstrapUserTenantStore(tenantDomain, directorySource); + importBootstrapSystemTenantStore(tenantDomain, directorySource); + importBootstrapVersionTenantStore(tenantDomain, directorySource); + importBootstrapSpacesArchiveTenantStore(tenantDomain, directorySource); + importBootstrapSpacesModelsTenantStore(tenantDomain, directorySource); + importBootstrapSpacesTenantStore(tenantDomain, directorySource); + + // notify listeners that tenant has been created & hence enabled + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + + // bootstrap workflows + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } + + // bootstrap modules (if any) + moduleService.startModules(); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + + logger.info("Tenant imported: " + tenantDomain); + } + + public boolean existsTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + return (getTenantAttributes(tenantDomain) != null); + } + + private void putTenantAttributes(String tenantDomain, Tenant tenant) + { + if (! attributeService.exists(TENANTS_ATTRIBUTE_PATH)) + { + // bootstrap + attributeService.setAttribute("", TENANTS_ATTRIBUTE_PATH, new MapAttributeValue()); + } + + MapAttribute tenantProps = new MapAttributeValue(); + tenantProps.put(TENANT_ATTRIBUTE_ENABLED, new BooleanAttributeValue(tenant.isEnabled())); + tenantProps.put(TENANT_ROOT_CONTENT_STORE_DIR, new StringAttributeValue(tenant.getRootContentStoreDir())); + + attributeService.setAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain, tenantProps); + + // update tenant status cache + ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); + } + + private Tenant getTenantAttributes(String tenantDomain) + { + if (attributeService.exists(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain)) + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain); + if (map != null) + { + return new Tenant(tenantDomain, + map.get(TENANT_ATTRIBUTE_ENABLED).getBooleanValue(), + map.get(TENANT_ROOT_CONTENT_STORE_DIR).getStringValue()); + } + } + + return null; + } + + public void enableTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + if (isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already enabled: " + tenantDomain); + } + + enableTenant(tenantDomain, true); + } + + private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, true, tenant.getRootContentStoreDir()); // enable + putTenantAttributes(tenantDomain, tenant); + + if (notifyTenantDeployers) + { + // notify listeners that tenant has been enabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + } + + logger.info("Tenant enabled: " + tenantDomain); + } + + public void disableTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + if (! isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already disabled: " + tenantDomain); + } + + disableTenant(tenantDomain, true); + } + + public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + if (notifyTenantDeployers) + { + // notify listeners that tenant has been disabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + } + + // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, false, tenant.getRootContentStoreDir()); // disable + putTenantAttributes(tenantDomain, tenant); + + logger.info("Tenant disabled: " + tenantDomain); + } + + public boolean isEnabledTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + if (tenant != null) + { + return tenant.isEnabled(); + } + + return false; + } + + protected String getRootContentStoreDir(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + if (tenant != null) + { + return tenant.getRootContentStoreDir(); + } + + return null; + } + + protected void putRootContentStoreDir(String tenantDomain, String rootContentStoreDir) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, tenant.isEnabled(), rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + } + + public Tenant getTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); + } + + /** + * @see TenantAdminService.deleteTenant() + */ + public void deleteTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + else + { + try + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + List workflowDefs = workflowService.getDefinitions(); + if (workflowDefs != null) + { + for (WorkflowDefinition workflowDef : workflowDefs) + { + workflowService.undeployDefinition(workflowDef.getId()); + } + } + + List messageResourceBundles = repoAdminService.getMessageBundles(); + if (messageResourceBundles != null) + { + for (String messageResourceBundle : messageResourceBundles) + { + repoAdminService.undeployMessageBundle(messageResourceBundle); + } + } + + List models = repoAdminService.getModels(); + if (models != null) + { + for (RepoModelDefinition model : models) + { + repoAdminService.undeployModel(model.getRepoName()); + } + } + + return null; + } + }, getSystemUser(tenantDomain)); + + final String tenantAdminUser = getTenantAdminUser(tenantDomain); + + // delete tenant-specific stores + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER))); + + + // notify listeners that tenant has been deleted & hence disabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + + // remove tenant + attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); + } + catch (Throwable t) + { + throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); + } + } + } + + /** + * @see TenantAdminService.getAllTenants() + */ + public List getAllTenants() + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH); + + List tenants = new ArrayList(); + + if (map != null) + { + // note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache + Set tenantDomains = map.keySet(); + + for (String tenantDomain : tenantDomains) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenants.add(new Tenant(tenantDomain, tenant.isEnabled(), tenant.getRootContentStoreDir())); + } + } + + return tenants; // list of tenants or empty list + } + + private void importBootstrapSystemTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Version Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_system.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap"); + systemImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); + } + + private void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific System Store + StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); + StoreRef tenantBootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + systemImporterBootstrap.setStoreUrl(tenantBootstrapStoreRef.toString()); + + // override default property (workspace://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)).toString()); + systemImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + systemImporterBootstrap.bootstrap(); + + // reset since systemImporter is singleton (hence reused) + systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(tenantBootstrapStoreRef)); + } + + private void importBootstrapUserTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific User Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_users.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); + userImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); + } + + private void bootstrapUserTenantStore(ImporterBootstrap userImporterBootstrap, String tenantDomain, char[] tenantAdminRawPassword) + { + // Bootstrap Tenant-Specific User Store + StoreRef bootstrapStoreRef = userImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + userImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override admin username property + Properties props = userImporterBootstrap.getConfiguration(); + props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); + + if (tenantAdminRawPassword != null) + { + String salt = null; // GUID.generate(); + props.put("alfresco_user_store.adminpassword", passwordEncoder.encodePassword(new String(tenantAdminRawPassword), salt)); + } + + userImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void importBootstrapVersionTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Version Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_versions2.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap"); + versionImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); + } + + private void bootstrapVersionTenantStore(ImporterBootstrap versionImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific Version Store + StoreRef bootstrapStoreRef = versionImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + versionImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + versionImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void importBootstrapSpacesArchiveTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Archive Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces_archive.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap"); + spacesArchiveImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); + } + + private void bootstrapSpacesArchiveTenantStore(ImporterBootstrap spacesArchiveImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific Spaces Archive Store + StoreRef bootstrapStoreRef = spacesArchiveImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + spacesArchiveImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override default property (archive://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(bootstrapStoreRef.toString()); + spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + spacesArchiveImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void importBootstrapSpacesModelsTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_models.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); + spacesImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + } + + private void importBootstrapSpacesTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces.acp"); + bootstrapView.put("uuidBinding", "UPDATE_EXISTING"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); + spacesImporterBootstrap.setBootstrapViews(bootstrapViews); + + spacesImporterBootstrap.setUseExistingStore(true); + + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + } + + private void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific Spaces Store + StoreRef bootstrapStoreRef = spacesImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + spacesImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override admin username property + Properties props = spacesImporterBootstrap.getConfiguration(); + props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); + + // override guest username property + props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); + + spacesImporterBootstrap.bootstrap(); + + // calculate any missing usages + UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)ctx.getBean(UserUsageBootstrapJob.KEY_COMPONENT); + userUsageTrackingComponent.bootstrapInternal(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + public void deployTenants(final TenantDeployer deployer, Log logger) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + if (logger == null) + { + throw new AlfrescoRuntimeException("Logger must be provided"); + } + + if (tenantService.isEnabled()) + { + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationContext.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + finally + { + authenticationContext.clearCurrentSecurityContext(); + } + + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // deploy within context of tenant domain + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // init the service within tenant context + deployer.init(); + return null; + } + }, getSystemUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Deployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant deploy failure should not necessarily affect other tenants + } + } + } + } + } + + public void undeployTenants(final TenantDeployer deployer, Log logger) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("Deployer must be provided"); + } + if (logger == null) + { + throw new AlfrescoRuntimeException("Logger must be provided"); + } + + if (tenantService.isEnabled()) + { + UserTransaction userTransaction = transactionService.getUserTransaction(); + authenticationContext.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationContext.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + + try + { + AuthenticationUtil.pushAuthentication(); + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // undeploy within context of tenant domain + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // destroy the service within tenant context + deployer.destroy(); + return null; + } + }, getSystemUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Undeployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant undeploy failure should not necessarily affect other tenants + } + } + } + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + } + + public void register(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("TenantDeployer must be provided"); + } + + if (! tenantDeployers.contains(deployer)) + { + tenantDeployers.add(deployer); + } + } + + public void unregister(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("TenantDeployer must be provided"); + } + + if (tenantDeployers != null) + { + tenantDeployers.remove(deployer); + } + } + + public void register(WorkflowDeployer workflowDeployer) + { + if (workflowDeployer == null) + { + throw new AlfrescoRuntimeException("WorkflowDeployer must be provided"); + } + + if (! workflowDeployers.contains(workflowDeployer)) + { + workflowDeployers.add(workflowDeployer); + } + } + + public void resetCache(String tenantDomain) + { + if (existsTenant(tenantDomain)) + { + if (isEnabledTenant(tenantDomain)) + { + enableTenant(tenantDomain); + } + else + { + disableTenant(tenantDomain); + } + } + else + { + throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); + } + } + + private void initTenant(String tenantDomain, String rootContentStoreDir) + { + validateTenantName(tenantDomain); + + if (existsTenant(tenantDomain)) + { + throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); + } + + if (rootContentStoreDir == null) + { + rootContentStoreDir = tenantFileContentStore.getDefaultRootDir(); + } + else + { + File tenantRootDir = new File(rootContentStoreDir); + if ((tenantRootDir.exists()) && (tenantRootDir.list().length != 0)) + { + throw new AlfrescoRuntimeException("Tenant root directory is not empty: " + rootContentStoreDir); + } + } + + // init - need to enable tenant (including tenant service) before stores bootstrap + Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + } + + private void validateTenantName(String tenantDomain) + { + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + if (tenantDomain.length() > MAX_LEN) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must be less than " + MAX_LEN + " characters)"); + } + + if (! Pattern.matches(REGEX_CONTAINS_ALPHA, tenantDomain)) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must contain at least one alpha character)"); + } + + String[] dnsLabels = tenantDomain.split("\\."); + if (dnsLabels.length != 0) + { + for (int i = 0; i < dnsLabels.length; i++) + { + if (! Pattern.matches(REGEX_VALID_DNS_LABEL, dnsLabels[i])) + { + throw new IllegalArgumentException(dnsLabels[i] + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); + } + } + } + else + { + if (! Pattern.matches(REGEX_VALID_DNS_LABEL, tenantDomain)) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); + } + } + } + + // tenant deployer/user services delegated to tenant service + + public boolean isEnabled() + { + return tenantService.isEnabled(); + } + + public String getCurrentUserDomain() + { + return tenantService.getCurrentUserDomain(); + } + + public String getUserDomain(String username) + { + return tenantService.getUserDomain(username); + } + + public String getBaseNameUser(String username) + { + return tenantService.getBaseNameUser(username); + } + + public String getDomainUser(String baseUsername, String tenantDomain) + { + return tenantService.getDomainUser(baseUsername, tenantDomain); + } + + public String getDomain(String name) + { + return tenantService.getDomain(name); + } + + // local helpers + + public String getBaseAdminUser() + { + // default for backwards compatibility only - eg. upgrade of existing MT instance (mt-admin-context.xml.sample) + if (baseAdminUsername != null) + { + return baseAdminUsername; + } + return AuthenticationUtil.getAdminUserName(); + } + + private String getSystemUser(String tenantDomain) + { + return tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + } + + private String getTenantAdminUser(String tenantDomain) + { + + return tenantService.getDomainUser(getBaseAdminUser(), tenantDomain); + } + + private String getTenantGuestUser(String tenantDomain) + { + return authenticationContext.getGuestUserName(tenantDomain); + } +} diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index 8536abc602..a68394bcfe 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -34,7 +34,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ContentData; @@ -73,7 +73,7 @@ public class ContentUsageImpl implements ContentUsageService, private PersonService personService; private PolicyComponent policyComponent; private UsageService usageService; - private AuthenticationComponent authenticationComponent; + private AuthenticationContext authenticationContext; private TenantService tenantService; private boolean enabled = true; @@ -100,9 +100,9 @@ public class ContentUsageImpl implements ContentUsageService, this.policyComponent = policyComponent; } - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + public void setAuthenticationContext(AuthenticationContext authenticationContext) { - this.authenticationComponent = authenticationComponent; + this.authenticationContext = authenticationContext; } public void setTenantService(TenantService tenantService) @@ -344,7 +344,7 @@ public class ContentUsageImpl implements ContentUsageService, private void incrementUserUsage(String userName, long contentSize, NodeRef contentNodeRef) { - if (! authenticationComponent.isSystemUserName(userName)) + if (! authenticationContext.isSystemUserName(userName)) { // increment usage - add positive delta if (logger.isDebugEnabled()) logger.debug("incrementUserUsage: username="+userName+", contentSize="+contentSize+", contentNodeRef="+contentNodeRef); @@ -376,7 +376,7 @@ public class ContentUsageImpl implements ContentUsageService, private void decrementUserUsage(String userName, long contentSize, NodeRef contentNodeRef) { - if (! authenticationComponent.isSystemUserName(userName)) + if (! authenticationContext.isSystemUserName(userName)) { // decrement usage - add negative delta if (logger.isDebugEnabled()) logger.debug("decrementUserUsage: username="+userName+", contentSize="+contentSize+", contentNodeRef="+contentNodeRef); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 0928f616f7..60c475a03b 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -37,7 +37,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.dictionary.DictionaryBootstrap; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.RepositoryLocation; -import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantAdminService; @@ -81,7 +81,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean private boolean allowWrite = true; private TransactionService transactionService; private WorkflowService workflowService; - private AuthenticationComponent authenticationComponent; + private AuthenticationContext authenticationContext; private DictionaryDAO dictionaryDAO; private List workflowDefinitions; private List models = new ArrayList(); @@ -130,11 +130,11 @@ public class WorkflowDeployer extends AbstractLifecycleBean /** * Set the authentication component * - * @param authenticationComponent + * @param authenticationContext */ - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + public void setAuthenticationContext(AuthenticationContext authenticationContext) { - this.authenticationComponent = authenticationComponent; + this.authenticationContext = authenticationContext; } /** @@ -234,7 +234,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean { throw new ImporterException("Transaction Service must be provided"); } - if (authenticationComponent == null) + if (authenticationContext == null) { throw new ImporterException("Authentication Component must be provided"); } @@ -243,10 +243,10 @@ public class WorkflowDeployer extends AbstractLifecycleBean throw new ImporterException("Workflow Service must be provided"); } - String currentUser = authenticationComponent.getCurrentUserName(); + String currentUser = authenticationContext.getCurrentUserName(); if (currentUser == null) { - authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + authenticationContext.setSystemUserAsCurrentUser(); } UserTransaction userTransaction = transactionService.getUserTransaction(); @@ -339,7 +339,7 @@ public class WorkflowDeployer extends AbstractLifecycleBean { if (currentUser == null) { - authenticationComponent.clearCurrentSecurityContext(); + authenticationContext.clearCurrentSecurityContext(); } } }