diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index a002ec43c2..1cfd4ac630 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -72,7 +72,7 @@ - + ${orphanReaper.lockRefreshTime} @@ -245,4 +245,21 @@ + + + + + + + + + + + + + + ${wcm.rename.max.time.milliseconds} + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index aa0959bf98..87db3291ce 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -95,6 +95,7 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-ActivityTables.sql classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-UsageTables.sql classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-SubscriptionTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-TenantTables.sql @@ -135,6 +136,7 @@ + @@ -832,9 +834,6 @@ - - - diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 314a9f26b6..1701cc7ce0 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -1346,5 +1346,38 @@ + + + + + + + + + + + + + + org.alfresco.cache.tenantEntityCache + + + + + + + + + + + + + org.alfresco.tenantsTransactionalCache + + + + + + diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 8e7b01d457..e33d1262b0 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -306,4 +306,9 @@ + + + + + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-TenantTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-TenantTables.sql new file mode 100644 index 0000000000..9aa36b856f --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-TenantTables.sql @@ -0,0 +1,30 @@ +-- +-- Title: Tenant tables +-- Database: MySQL InnoDB +-- Since: V4.0 Schema 5030 +-- Author: janv +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE TABLE alf_tenant ( + tenant_domain VARCHAR(75) NOT NULL, + version BIGINT NOT NULL, + enabled BIT NOT NULL, + tenant_name VARCHAR(75), + content_root VARCHAR(255), + db_url VARCHAR(255), + PRIMARY KEY (tenant_domain) +) ENGINE=InnoDB; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-TenantTables'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-TenantTables', 'Manually executed script upgrade V4.0: Tenant Tables', + 0, 6004, -1, 6005, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-TenantTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-TenantTables.sql new file mode 100644 index 0000000000..9eb03e02dd --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-TenantTables.sql @@ -0,0 +1,30 @@ +-- +-- Title: Tenant tables +-- Database: PostgreSQL +-- Since: V4.0 Schema 5030 +-- Author: janv +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE TABLE alf_tenant ( + tenant_domain VARCHAR(75) NOT NULL, + version INT8 NOT NULL, + enabled BOOL NOT NULL, + tenant_name VARCHAR(75), + content_root VARCHAR(255), + db_url VARCHAR(255), + PRIMARY KEY (tenant_domain) +); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-TenantTables'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-TenantTables', 'Manually executed script upgrade V4.0: Tenant Tables', + 0, 6004, -1, 6005, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index a763850091..7e81230736 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -330,7 +330,7 @@ + + + + + + + + + + + + + + + + + + + + + + + ${alfresco_user_store.adminusername} + + + + + + alfresco.messages.tenant-interpreter-help + + + + + diff --git a/config/alfresco/extension/mt/mt-admin-context.xml.sample b/config/alfresco/extension/mt/mt-admin-context.xml.sample index 0a138f0c35..d492032b1b 100644 --- a/config/alfresco/extension/mt/mt-admin-context.xml.sample +++ b/config/alfresco/extension/mt/mt-admin-context.xml.sample @@ -14,25 +14,25 @@ - - - - - - - + + + + + + + ${alfresco_user_store.adminusername} - - - + + + alfresco.messages.tenant-interpreter-help - + diff --git a/config/alfresco/extension/mt/mt-contentstore-context.xml b/config/alfresco/extension/mt/mt-contentstore-context.xml new file mode 100644 index 0000000000..6fc64c92ca --- /dev/null +++ b/config/alfresco/extension/mt/mt-contentstore-context.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + org.alfresco.cache.tenantFileStoresCache + + + + + + + + + + + + ${dir.contentstore} + + + + + + + + + + + + + + ${system.content.eagerOrphanCleanup} + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/mt/mt-contentstore-context.xml.sample b/config/alfresco/extension/mt/mt-contentstore-context.xml.sample index 6fc64c92ca..2f444d26e8 100644 --- a/config/alfresco/extension/mt/mt-contentstore-context.xml.sample +++ b/config/alfresco/extension/mt/mt-contentstore-context.xml.sample @@ -3,60 +3,13 @@ - - - + + + - - - - - - - - org.alfresco.cache.tenantFileStoresCache - - - + + + - - - - - - - ${dir.contentstore} - - - - - - - - - - - - - - ${system.content.eagerOrphanCleanup} - - - - - - - - - - - - - - - - - - diff --git a/config/alfresco/extension/mt/mt-context.xml b/config/alfresco/extension/mt/mt-context.xml new file mode 100644 index 0000000000..686026bd68 --- /dev/null +++ b/config/alfresco/extension/mt/mt-context.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + org.alfresco.cache.tenantsCache + + + + + + + + + + + + + org.alfresco.tenantsTransactionalCache + + + 100 + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/mt/mt-context.xml.sample b/config/alfresco/extension/mt/mt-context.xml.sample index 90645167f8..1ddff452ba 100644 --- a/config/alfresco/extension/mt/mt-context.xml.sample +++ b/config/alfresco/extension/mt/mt-context.xml.sample @@ -2,51 +2,17 @@ - - - - - - - - - - - - - - org.alfresco.cache.tenantsCache - - - - - - - - - - - - - org.alfresco.tenantsTransactionalCache - - - 10 - - - - - + - - + + - - + + - + \ No newline at end of file diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index fcf1104a40..20241c9436 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -162,6 +162,10 @@ Inbound settings from iBatis + + + + @@ -197,6 +201,7 @@ Inbound settings from iBatis + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/tenants-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/tenants-common-SqlMap.xml new file mode 100644 index 0000000000..1ffd591466 --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/tenants-common-SqlMap.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + insert into alf_tenant (tenant_domain, version, enabled, tenant_name, content_root, db_url) + values (#{tenantDomain}, #{version}, #{enabled}, #{tenantName}, #{contentRoot}, #{dbUrl}) + + + + delete from alf_tenant where tenant_domain = #{tenantDomain} + + + + + + + + + update + alf_tenant + set + enabled = #{enabled}, + content_root =#{contentRoot}, + db_url = #{dbUrl}, + tenant_name = #{tenantName}, + version = #{version} + where + tenant_domain = #{tenantDomain} + + and version = (#{version} - 1) + + + + \ No newline at end of file diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index ef86911e60..34594e226b 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -26,7 +26,7 @@ - + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 2a8a803cb9..cf42d81348 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -3091,5 +3091,16 @@ classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/ActivitiTaskIdIndexes.sql - + + + patch.db-V4.0-TenantTables + patch.schemaUpgradeScript.description + 0 + 6004 + 6005 + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-TenantTables.sql + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index b9018bd2d5..173f6e7b55 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -763,6 +763,9 @@ transferservice.receiver.lockRetryWait=100 # time" for both source and destination. Default 5 minutes. transferservice.receiver.lockTimeOut=300000 +# Max time allowed for WCM folder rename operation issued by external clients (CIFS, FTP) +wcm.rename.max.time.milliseconds=2000 + ; DM Receiever Properties ; ; The name of the DM Receiver target - you deploy to this target name diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index 023c023a1b..d5e5e7f2b1 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -68,7 +68,10 @@ - org.alfresco.repo.site.SiteServiceInternal + + org.alfresco.repo.site.SiteServiceInternal + org.alfresco.service.cmr.site.SiteService + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index a9aa890379..05c1ce7e9a 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -6,7 +6,7 @@ version.major=4 version.minor=1 -version.revision=1 +version.revision=0 version.label= # Edition label @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=6004 +version.schema=6005 diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java index 6011e7f23c..ddd7316366 100644 --- a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -1,3342 +1,3347 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . */ - -package org.alfresco.filesys.avm; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.StringTokenizer; - -import javax.transaction.UserTransaction; - -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; -import org.alfresco.filesys.config.ServerConfigurationBean; -import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver; -import org.alfresco.jlan.server.SrvSession; -import org.alfresco.jlan.server.auth.ClientInfo; -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.DirectoryNotEmptyException; -import org.alfresco.jlan.server.filesys.DiskInterface; -import org.alfresco.jlan.server.filesys.FileAttribute; -import org.alfresco.jlan.server.filesys.FileExistsException; -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.FileStatus; -import org.alfresco.jlan.server.filesys.NetworkFile; -import org.alfresco.jlan.server.filesys.PathNotFoundException; -import org.alfresco.jlan.server.filesys.SearchContext; -import org.alfresco.jlan.server.filesys.TreeConnection; -import org.alfresco.jlan.server.filesys.cache.FileState; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; -import org.alfresco.jlan.server.filesys.pseudo.PseudoFolderNetworkFile; -import org.alfresco.jlan.util.StringList; -import org.alfresco.jlan.util.WildCard; -import org.alfresco.model.WCMAppModel; -import org.alfresco.repo.avm.CreateStoreTxnListener; -import org.alfresco.repo.avm.CreateVersionTxnListener; -import org.alfresco.repo.avm.PurgeStoreTxnListener; -import org.alfresco.repo.avm.PurgeVersionTxnListener; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.avm.AVMBadArgumentException; -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.AVMService; -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.avm.locking.AVMLockingException; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -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.security.AuthenticationService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.wcm.sandbox.SandboxConstants; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.config.ConfigElement; - -/** - * AVM Repository Filesystem Driver Class - *

- * Provides a filesystem interface for various protocols such as SMB/CIFS and FTP. - * - * @author GKSpencer - */ -public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface -{ - // Logging - - private static final Log logger = LogFactory.getLog(AVMDiskDriver.class); - - // Configuration key names - - private static final String KEY_STORE = "storePath"; - private static final String KEY_VERSION = "version"; - private static final String KEY_CREATE = "createStore"; - - // AVM path seperator - - public static final char AVM_SEPERATOR = '/'; - public static final String AVM_SEPERATOR_STR = "/"; - - // Define client role names - - public static final String RoleContentManager = "ContentManager"; - public static final String RoleWebProject = "WebProject"; - public static final String RoleNotWebAuthor = "NotWebAuthor"; - - // Content manager web project role - - private static final String ROLE_CONTENT_MANAGER = "ContentManager"; - - // File status values used in the file state cache - - public static final int FileUnknown = FileStatus.Unknown; - public static final int FileNotExist = FileStatus.NotExist; - public static final int FileExists = FileStatus.FileExists; - public static final int DirectoryExists = FileStatus.DirectoryExists; - - public static final int CustomFileStatus= FileStatus.MaxStatus + 1; - - // Services and helpers - - private AVMService m_avmService; - private MimetypeService m_mimetypeService; - private AuthenticationComponent m_authComponent; - private AuthenticationService m_authService; - private NodeService m_nodeService; - - // AVM listeners - - private CreateStoreTxnListener m_createStoreListener; - private PurgeStoreTxnListener m_purgeStoreListener; - private CreateVersionTxnListener m_createVerListener; - private PurgeVersionTxnListener m_purgeVerListener; - - // Web project store - - private String m_webProjectStore; - - /** - * Default constructor - */ - public AVMDiskDriver() - { - } - - /** - * Return the AVM service - * - * @return AVMService - */ - public final AVMService getAvmService() - { - return m_avmService; - } - - /** - * Return the authentication service - * - * @return AuthenticationService - */ - public final AuthenticationService getAuthenticationService() - { - return m_authService; - } - - /** - * Set the AVM service - * - * @param avmService - * AVMService - */ - public void setAvmService(AVMService avmService) - { - m_avmService = avmService; - } - - /** - * Set the authentication component - * - * @param authComponent - * AuthenticationComponent - */ - public void setAuthenticationComponent(AuthenticationComponent authComponent) - { - m_authComponent = authComponent; - } - - /** - * Set the authentication service - * - * @param authService - * AuthenticationService - */ - public void setAuthenticationService(AuthenticationService authService) - { - m_authService = authService; - } - - /** - * Set the mimetype service - * - * @param mimetypeService - * MimetypeService - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - m_mimetypeService = mimetypeService; - } - - /** - * Set the node service - * - * @param nodeService NodeService - */ - public void setNodeService(NodeService nodeService) - { - m_nodeService = nodeService; - } - - /** - * Set the create store listener - * - * @param createStoreListener - * CreateStoreTxnListener - */ - public void setCreateStoreListener(CreateStoreTxnListener createStoreListener) - { - m_createStoreListener = createStoreListener; - } - - /** - * Set the purge store listener - * - * @param purgeStoreListener - * PurgeStoreTxnListener - */ - public void setPurgeStoreListener(PurgeStoreTxnListener purgeStoreListener) - { - m_purgeStoreListener = purgeStoreListener; - } - - /** - * Set the create version listener - * - * @param createVersionListener - * CreateVersionTxnListener - */ - public void setCreateVersionListener(CreateVersionTxnListener createVersionListener) - { - m_createVerListener = createVersionListener; - } - - /** - * Set the purge version listener - * - * @param purgeVersionListener - * PurgeVersionTxnListener - */ - public void setPurgeVersionListener(PurgeVersionTxnListener purgeVersionListener) - { - m_purgeVerListener = purgeVersionListener; - } - - /** - * Set the web project store - * - * @param webStore String - */ - public void setWebProjectStore(String webStore) - { - m_webProjectStore = webStore; - } - - /** - * Parse and validate the parameter string and create a device context object for this instance of the shared - * device. - * - * @param shareName String - * @param cfg ConfigElement - * @return DeviceContext - * @exception DeviceContextException - */ - public DeviceContext createContext(String shareName, ConfigElement cfg) - throws DeviceContextException - { - AVMContext context = null; - - try - { - // Check if the share is a virtualization view - - ConfigElement virtElem = cfg.getChild("virtualView"); - if (virtElem != null) - { - // Check if virtualization view show options have been specified - - int showOptions = AVMContext.ShowStagingStores + AVMContext.ShowAuthorStores; - - String showAttr = virtElem.getAttribute( "stores"); - if ( showAttr != null) - { - // Split the show options string - - StringTokenizer tokens = new StringTokenizer( showAttr, ","); - StringList optList = new StringList(); - - while ( tokens.hasMoreTokens()) - optList.addString( tokens.nextToken().trim().toLowerCase()); - - // Build the show options mask - - showOptions = 0; - - if ( optList.containsString("normal")) - showOptions += AVMContext.ShowNormalStores; - - if ( optList.containsString("site")) - showOptions += AVMContext.ShowSiteStores; - - if ( optList.containsString("author")) - showOptions += AVMContext.ShowAuthorStores; - - if ( optList.containsString("preview")) - showOptions += AVMContext.ShowPreviewStores; - - if ( optList.containsString("staging")) - showOptions += AVMContext.ShowStagingStores; - } - else if ( cfg.getChild("showAllSandboxes") != null) - { - // Old style show options - - showOptions = AVMContext.ShowNormalStores + AVMContext.ShowSiteStores + - AVMContext.ShowAuthorStores + AVMContext.ShowPreviewStores + - AVMContext.ShowStagingStores; - } - - // Create the context - - context = new AVMContext(shareName, showOptions, this); - - // Check if the admin user should be allowed to write to the web project staging stores - - if ( cfg.getChild("adminWriteable") != null) - context.setAllowAdminStagingWrites( true); - - } - else - { - // Get the store path - - 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 storePath = storeElement.getValue(); - - // Get the version if specified, or default to the head version - - int version = AVMContext.VERSION_HEAD; - - ConfigElement versionElem = cfg.getChild(KEY_VERSION); - if (versionElem != null) - { - // Check if the version is valid - - if (versionElem.getValue() == null || versionElem.getValue().length() == 0) - throw new DeviceContextException("Store version not specified"); - - // Validate the version id - - try - { - version = Integer.parseInt(versionElem.getValue()); - } - catch (NumberFormatException ex) - { - throw new DeviceContextException("Invalid store version specified, " - + versionElem.getValue()); - } - - // Range check the version id - - if (version < 0 && version != AVMContext.VERSION_HEAD) - throw new DeviceContextException("Invalid store version id specified, " + version); - } - - // Create the context - - context = new AVMContext(shareName, storePath, version); - - // Check if the create flag is enabled - - ConfigElement createStore = cfg.getChild(KEY_CREATE); - context.setCreateStore(createStore != null); - - // Enable file state caching - - //context.enableStateCache( true); - } - - } - catch (Exception ex) - { - logger.error("Error during create context", ex); - - // Rethrow the exception - - throw new DeviceContextException("Driver setup error, " + ex.getMessage()); - } - - // Register the context bean - registerContext(context); - - // Return the context for this shared filesystem - return context; - } - - /** - * Register a device context object for this instance of the shared - * device. - * - * @param context the device context - * @param serverConfig ServerConfigurationBean - * @exception DeviceContextException - */ - @Override - public void registerContext(DeviceContext ctx) - throws DeviceContextException - { - super.registerContext(ctx); - - AVMContext context = (AVMContext)ctx; - // Use the system user as the authenticated context for the filesystem initialization - - try - { - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - - // Wrap the initialization in a transaction - - UserTransaction tx = getTransactionService().getUserTransaction(false); - - try - { - // Start the transaction - - if (tx != null) - tx.begin(); - - // Check if the share is a virtualization view - - if (context.isVirtualizationView()) - { - // Enable file state caching - -// context.enableStateCache(serverConfig, true); - - // Plug the virtualization view context into the various store/version call back listeners - // so that store/version pseudo folders can be kept in sync with AVM - - m_createStoreListener.addCallback(context); - m_purgeStoreListener.addCallback(context); - - m_createVerListener.addCallback(context); - m_purgeVerListener.addCallback(context); - - // Create the file state for the root path, this will build the store pseudo folder list - - findPseudoState( new AVMPath( ""), context); - } - else - { - // Get the store path - String storePath = context.getStorePath(); - - // Get the version - int version = context.isVersion(); - - // Validate the store path - - AVMNodeDescriptor rootNode = m_avmService.lookup(version, storePath); - if (rootNode == null) - { - // Check if the store should be created - - if (!context.getCreateStore()|| version != AVMContext.VERSION_HEAD) - throw new DeviceContextException("Invalid store path/version, " - + storePath + " (" + version + ")"); - - // Parse the store path - - String storeName = null; - String path = null; - - int pos = storePath.indexOf(":/"); - if (pos != -1) - { - storeName = storePath.substring(0, pos); - if (storePath.length() > pos) - path = storePath.substring(pos + 2); - } - else - storeName = storePath; - - // Check if the store exists - - AVMStoreDescriptor storeDesc = null; - - try - { - storeDesc = m_avmService.getStore(storeName); - } - catch (AVMNotFoundException ex) - { - } - - // Create a new store if it does not exist - - if (storeDesc == null) - m_avmService.createStore(storeName); - - // Check if there is an optional path - - if (path != null) - { - // Split the path - - StringTokenizer tokens = new StringTokenizer(path, AVMPath.AVM_SEPERATOR_STR); - StringList paths = new StringList(); - - while (tokens.hasMoreTokens()) - paths.addString(tokens.nextToken()); - - // Create the path, or folders that do not exist - - AVMPath curPath = new AVMPath(storeName, version, FileName.DOS_SEPERATOR_STR); - AVMNodeDescriptor curDesc = m_avmService.lookup(curPath.getVersion(), curPath.getAVMPath()); - - // Walk the path checking creating each folder as required - - for (int i = 0; i < paths.numberOfStrings(); i++) - { - AVMNodeDescriptor nextDesc = null; - - try - { - // Check if the child folder exists - - nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i)); - } - catch (AVMNotFoundException ex) - { - } - - // Check if the folder exists - - if (nextDesc == null) - { - // Create the new folder - - m_avmService.createDirectory(curPath.getAVMPath(), paths.getStringAt(i)); - - // Get the details of the new folder - - nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i)); - } - else if (nextDesc.isFile()) - throw new DeviceContextException("Path element error, not a folder, " - + paths.getStringAt(i)); - - // Step to the next level - - curPath.parsePath(storeName, version, curPath.getRelativePath() - + paths.getStringAt(i) + FileName.DOS_SEPERATOR_STR); - curDesc = nextDesc; - } - } - - // Validate the store path again - - rootNode = m_avmService.lookup(version, storePath); - if (rootNode == null) - throw new DeviceContextException("Failed to create new store " + storePath); - } - - // Enable file state caching - -// context.enableStateCache(serverConfig, true); - } - - // Commit the transaction - - tx.commit(); - tx = null; - } - catch (Exception ex) - { - logger.error("Error during create context", ex); - - // Rethrow the exception - - throw new DeviceContextException("Driver setup error, " + ex.getMessage(), ex); - } - finally - { - // 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); - } - } - } - - // Return the context for this shared filesystem - } - finally - { - AuthenticationUtil.popAuthentication(); - } - } - - /** - * Return a list of the available AVM store names - * - * @return StringList - */ - public final StringList getAVMStoreNames() - { - // Use the system user as the authenticated context to get the AVM store list - - String currentUser = m_authComponent.getCurrentUserName(); - try - { - m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); - - // Wrap the service request in a transaction - - UserTransaction tx = getTransactionService().getUserTransaction(false); - - StringList storeNames = new StringList(); - - try - { - // Start the transaction - - if (tx != null) - tx.begin(); - - // Get the list of AVM stores - - List storeList = m_avmService.getStores(); - - if (storeList != null) - { - for (AVMStoreDescriptor storeDesc : storeList) - storeNames.addString(storeDesc.getName()); - } - - // Commit the transaction - if (tx != null) - tx.commit(); - tx = null; - } - catch (Exception ex) - { - logger.error("Error getting store names", ex); - } - finally - { - // 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); - } - } - } - - // Return the list of AVM store names - - return storeNames; - } - finally - { - m_authComponent.setCurrentUser(currentUser); - } - } - - /** - * Get the properties for a store - * - * @param storeName - * String - * @return Map - */ - protected final Map getAVMStoreProperties(String storeName) - { - // Use the system user as the authenticated context to get the AVM store properties - - String currentUser = m_authComponent.getCurrentUserName(); - try - { - m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); - - // Wrap the service request in a transaction - - UserTransaction tx = getTransactionService().getUserTransaction(false); - - Map properties = null; - - try - { - // Start the transaction - - if (tx != null) - tx.begin(); - - // Get the list of properties for AVM store - - properties = m_avmService.getStoreProperties(storeName); - - // Commit the transaction - - if (tx != null) - tx.commit(); - tx = null; - } - catch (Exception ex) - { - logger.error("Error getting store properties", ex); - } - finally - { - // 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); - } - } - } - - // Return the list of AVM store properties - - return properties; - } - finally - { - m_authComponent.setCurrentUser(currentUser); - } - } - - /** - * Build the full store path for a file/folder using the share relative path - * - * @param ctx AVMContext - * @param path String - * @param sess SrvSession - * @return AVMPath - * @exception AccessDeniedException - */ - protected final AVMPath buildStorePath(AVMContext ctx, String path, SrvSession sess) - throws AccessDeniedException - { - // Check if the AVM filesystem is a normal or virtualization view - - AVMPath avmPath = null; - - if (ctx.isVirtualizationView()) - { - // Create a path for the virtualization view - - avmPath = new AVMPath(path); - - // Check that the user has access to the path - - checkPathAccess( avmPath, ctx, sess); - } - else - { - // Create a path to a single store/version - - avmPath = new AVMPath(ctx.getStorePath(), ctx.isVersion(), path); - } - - // Return the path - - return avmPath; - } - - /** - * Close the file. - * - * @param sess - * Server session - * @param tree - * Tree connection. - * @param file - * Network file context. - * @exception java.io.IOException - * If an error occurs. - */ - public void closeFile(final SrvSession sess, final TreeConnection tree, final NetworkFile file) throws java.io.IOException - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Close file " + file.getFullName()); - - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - // Close the file - - file.closeFile(); - - // Check if the file/directory is marked for delete - - if (file.hasDeleteOnClose()) - { - - // Check for a file or directory - - if (file.isDirectory()) - deleteDirectory(sess, tree, file.getFullName()); - else - deleteFile(sess, tree, file.getFullName()); - } - return null; - }}); - } - - /** - * 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 java.io.IOException - { - // Check if the filesystem is writable - - AVMContext ctx = (AVMContext) tree.getContext(); - if (ctx.isVersion() != AVMContext.VERSION_HEAD) - throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable"); - - // Split the path to get the new folder name and relative path - - final String[] paths = FileName.splitPath(params.getPath()); - - // Convert the relative path to a store path - - final AVMPath storePath = buildStorePath(ctx, paths[0], sess); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]); - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView()) - { - if (storePath.isReadOnlyPseudoPath()) - throw new AccessDeniedException("Cannot create folder in store/version layer, " + params.getPath()); - else if ( storePath.isReadOnlyAccess()) - throw new AccessDeniedException("Cannot create folder " + params.getPath() + ", read-only path"); - } - - // Create a new file - - try - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - // Create the new file entry - - m_avmService.createDirectory(storePath.getAVMPath(), paths[1]); - - return null; - }}); - } - catch (AVMExistsException ex) - { - throw new FileExistsException(params.getPath()); - } - catch (AVMNotFoundException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMWrongTypeException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMBadArgumentException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMLockingException ex) - { - throw new AccessDeniedException(params.getPath()); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new AccessDeniedException(params.getPath()); - } - } - - /** - * 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(final SrvSession sess, TreeConnection tree, final FileOpenParams params) - throws java.io.IOException - { - // Check if the filesystem is writable - - final AVMContext ctx = (AVMContext) tree.getContext(); - - // Split the path to get the file name and relative path - - final String[] paths = FileName.splitPath(params.getPath()); - - // Convert the relative path to a store path - - final AVMPath storePath = buildStorePath(ctx, paths[0], sess); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]); - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView()) - { - if (storePath.isReadOnlyPseudoPath()) - throw new AccessDeniedException("Cannot create file in store/version layer, " + params.getPath()); - else if ( storePath.isReadOnlyAccess()) - throw new AccessDeniedException("Cannot create file " + params.getPath() + ", read-only path"); - } - else if (storePath.getVersion() != AVMContext.VERSION_HEAD) - { - throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable"); - } - - - try - { - // Create a new file - return doInWriteTransaction(sess, new CallableIO(){ - - public NetworkFile call() throws IOException - { - // Create the new file entry - - m_avmService.createFile(storePath.getAVMPath(), paths[1]).close(); - - // Get the new file details - - AVMPath fileStorePath = buildStorePath(ctx, params.getPath(), sess); - AVMNodeDescriptor nodeDesc = m_avmService.lookup(fileStorePath.getVersion(), fileStorePath.getAVMPath()); - - if (nodeDesc != null) - { - // Create the network file object for the new file - - AVMNetworkFile netFile = new AVMNetworkFile(nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(), - m_nodeService, m_avmService); - netFile.setGrantedAccess(NetworkFile.READWRITE); - netFile.setFullName(params.getPath()); - - netFile.setFileId(fileStorePath.generateFileId()); - - // Set the mime-type for the new file - - netFile.setMimeType(m_mimetypeService.guessMimetype(paths[1])); - return netFile; - } - return null; - }}); - } - catch (AVMExistsException ex) - { - throw new FileExistsException(params.getPath()); - } - catch (AVMNotFoundException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMWrongTypeException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMBadArgumentException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMLockingException ex) - { - throw new AccessDeniedException(params.getPath()); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new AccessDeniedException(params.getPath()); - } - } - - /** - * 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, final String dir) throws java.io.IOException - { - // Convert the relative path to a store path - - AVMContext ctx = (AVMContext) tree.getContext(); - - final String[] paths = FileName.splitPath(dir); - final AVMPath parentPath = buildStorePath(ctx, paths[0], sess); - final AVMPath dirPath = buildStorePath(ctx, dir, sess); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Delete directory, path=" + dir + ", dirPath=" + dirPath); - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView()) - { - if (parentPath.isPseudoPath()) - throw new AccessDeniedException("Cannot delete folder in store/version layer, " + dir); - else if ( parentPath.isReadOnlyAccess()) - throw new AccessDeniedException("Cannot delete folder " + dir + ", read-only path"); - } - - // Make sure the path is to a folder before deleting it - - try - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - AVMNodeDescriptor nodeDesc = m_avmService.lookup(dirPath.getVersion(), dirPath.getAVMPath()); - if (nodeDesc != null) - { - // Check that we are deleting a folder - - if (nodeDesc.isDirectory()) - { - // Make sure the directory is empty - - SortedMap fileList = m_avmService.getDirectoryListing(nodeDesc); - if (fileList != null && fileList.size() > 0) - throw new DirectoryNotEmptyException(dir); - - // Delete the folder - - m_avmService.removeNode(dirPath.getAVMPath()); - } - else - throw new IOException("Delete directory path is not a directory, " + dir); - } - return null; - }}); - } - catch (AVMNotFoundException ex) - { - throw new IOException("Directory not found, " + dir); - } - catch (AVMWrongTypeException ex) - { - throw new IOException("Invalid path, " + dir); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new AccessDeniedException("Access denied, " + dir); - } - } - - /** - * 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, final String name) throws java.io.IOException - { - // Convert the relative path to a store path - - AVMContext ctx = (AVMContext) tree.getContext(); - - final String[] paths = FileName.splitPath(name); - final AVMPath parentPath = buildStorePath(ctx, paths[0], sess); - final AVMPath filePath = buildStorePath(ctx, name, sess); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Delete file, path=" + name + ", filePath=" + filePath); - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView()) - { - if (parentPath.isPseudoPath()) - throw new AccessDeniedException("Cannot delete file in store/version layer, " + name); - else if ( parentPath.isReadOnlyAccess()) - throw new AccessDeniedException("Cannot delete file " + name + ", read-only path"); - } - - // Make sure the path is to a file before deleting it - - try - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - AVMNodeDescriptor nodeDesc = m_avmService.lookup(filePath.getVersion(), filePath.getAVMPath()); - if (nodeDesc != null) - { - // Check that we are deleting a file - - if (nodeDesc.isFile()) - { - // Delete the file - - m_avmService.removeNode(filePath.getAVMPath()); - } - else - throw new IOException("Delete file path is not a file, " + name); - } - return null; - }}); - } - catch (AVMNotFoundException ex) - { - throw new IOException("File not found, " + name); - } - catch (AVMWrongTypeException ex) - { - throw new IOException("Invalid path, " + name); - } - catch (AVMLockingException ex) - { - throw new AccessDeniedException("File locked, " + name); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new AccessDeniedException("Access denied, " + name); - } - } - - /** - * 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) - { - // Convert the relative path to a store path - - AVMContext ctx = (AVMContext) tree.getContext(); - AVMPath storePath = null; - - try - { - storePath = buildStorePath(ctx, name, sess); - } - catch ( AccessDeniedException ex) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("File exists check, path=" + name + " Access denied"); - - return FileStatus.NotExist; - } - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("File exists check, path=" + name + ", storePath=" + storePath); - - // Check if the path is valid - - int status = FileStatus.NotExist; - - if (storePath.isValid() == false) - return status; - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) - { - // Find the file state for the pseudo folder - - FileState fstate = findPseudoState(storePath, ctx); - - if (fstate != null) - { - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug(" Found pseudo file " + fstate); - - // Check if the pseudo file is a file or folder - - if (fstate.isDirectory()) - status = FileStatus.DirectoryExists; - else - status = FileStatus.FileExists; - } - else - { - // Invalid pseudo file path - - status = FileStatus.NotExist; - } - - // Return the file status - - return status; - } - - // Search for the file/folder - - beginReadTransaction( sess); - - AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); - - if (nodeDesc != null) - { - // Check if the path is to a file or folder - - if (nodeDesc.isDirectory()) - status = FileStatus.DirectoryExists; - else - status = FileStatus.FileExists; - } - - // Return the file status - - return status; - } - - /** - * 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 java.io.IOException - { - // Flush the file - - file.flushFile(); - } - - /** - * 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 sess, TreeConnection tree, String name) throws java.io.IOException - { - // Convert the relative path to a store path - - AVMContext ctx = (AVMContext) tree.getContext(); - AVMPath storePath = null; - - try - { - storePath = buildStorePath( ctx, name, sess); - } - catch ( Exception ex) - { - throw new FileNotFoundException( name); - } - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Get file information, path=" + name + ", storePath=" + storePath); - - // Check if hte path is valid - - if ( storePath.isValid() == false) - throw new FileNotFoundException( name); - - // Check if the filesystem is the virtualization view - - if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) - { - // Search for the pseudo path, to check for any new stores - - FileState fstate = findPseudoState( storePath, ctx); - - // Check if the search path is for the root, a store or version folder - - if ( storePath.isRootPath()) - { - // Return dummy file informatiom for the root folder, use cached timestamps - - FileInfo finfo = new FileInfo( name, 0L, FileAttribute.Directory); - - if ( fstate != null) { - finfo.setModifyDateTime( fstate.getModifyDateTime()); - finfo.setChangeDateTime( fstate.getModifyDateTime()); - } - - // Return the root folder file information - - return finfo; - } - else - { - // Find the pseudo file for the store/version folder - - PseudoFile psFile = findPseudoFolder( storePath, ctx); - if ( psFile != null) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( " Found pseudo file " + psFile); - return psFile.getFileInfo(); - } - else - throw new FileNotFoundException( name); - } - } - - // Search for the file/folder - - beginReadTransaction( sess); - - FileInfo info = null; - - try - { - AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath()); - - if ( nodeDesc != null) - { - // Create, and fill in, the file information - - info = new FileInfo(); - - info.setFileName( nodeDesc.getName()); - - if ( nodeDesc.isFile()) - { - info.setFileSize( nodeDesc.getLength()); - info.setAllocationSize((nodeDesc.getLength() + 512L) & 0xFFFFFFFFFFFFFE00L); - } - else - info.setFileSize( 0L); - - info.setAccessDateTime( nodeDesc.getAccessDate()); - info.setCreationDateTime( nodeDesc.getCreateDate()); - info.setModifyDateTime( nodeDesc.getModDate()); - info.setChangeDateTime( nodeDesc.getModDate()); - - // Build the file attributes - - int attr = 0; - - if ( nodeDesc.isDirectory()) - attr += FileAttribute.Directory; - - if ( nodeDesc.getName().startsWith( ".") || - nodeDesc.getName().equalsIgnoreCase( "Desktop.ini") || - nodeDesc.getName().equalsIgnoreCase( "Thumbs.db")) - attr += FileAttribute.Hidden; - - // Mark the file/folder as read-only if not the head version - - if ( ctx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess()) - attr += FileAttribute.ReadOnly; - - if ( attr == 0) - attr = FileAttribute.NTNormal; - - info.setFileAttributes( attr); - - // Set the file id - - info.setFileId( storePath.generateFileId()); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug(" File info=" + info); - } - } - catch ( AVMNotFoundException ex) - { - throw new FileNotFoundException( name); - } - catch ( AVMWrongTypeException ex) - { - throw new PathNotFoundException( name); - } - - // Return the file information - - return info; - } - - /** - * 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 java.io.IOException - { - // Check if the version indicates the head version, only the head is writable - - AVMContext avmCtx = (AVMContext) ctx; - return avmCtx.isVersion() == AVMContext.VERSION_HEAD ? true : false; - } - - /** - * Open a file on the file system. - * - * @param sess - * Server session - * @param tree - * Tree connection - * @param params - * File open parameters - * @return NetworkFile - * @exception java.io.IOException - * If an error occurs. - */ - public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws java.io.IOException - { - // Convert the relative path to a store path - - AVMContext ctx = (AVMContext) tree.getContext(); - AVMPath storePath = buildStorePath(ctx, params.getPath(), sess); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Open file params=" + params + ", storePath=" + storePath); - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) - { - // Check if the path is for the root, a store or version folder - - if (storePath.isRootPath()) - { - // Return a dummy file for the root folder - - return new PseudoFolderNetworkFile(FileName.DOS_SEPERATOR_STR); - } - else - { - // Find the pseudo file for the store/version folder - - PseudoFile psFile = findPseudoFolder(storePath, ctx); - if (psFile != null) - { - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug(" Found pseudo file " + psFile); - return psFile.getFile(params.getPath()); - } - else - return null; - } - } - - // Search for the file/folder - - beginReadTransaction( sess); - - AVMNetworkFile netFile = null; - - try - { - // Get the details of the file/folder - - AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); - - if (nodeDesc != null) - { - // Check if the filesystem is read-only and write access has been requested - - if (storePath.getVersion() != AVMContext.VERSION_HEAD - && (params.isReadWriteAccess() || params.isWriteOnlyAccess())) - throw new AccessDeniedException("File " + params.getPath() + " is read-only"); - - // Create the network file object for the opened file/folder - - netFile = new AVMNetworkFile(nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_nodeService, m_avmService); - - if (params.isReadOnlyAccess() || storePath.getVersion() != AVMContext.VERSION_HEAD) - netFile.setGrantedAccess(NetworkFile.READONLY); - else - netFile.setGrantedAccess(NetworkFile.READWRITE); - - netFile.setFullName(params.getPath()); - netFile.setFileId(storePath.generateFileId()); - - // Set the mime-type for the new file - - netFile.setMimeType(m_mimetypeService.guessMimetype(params.getPath())); - } - else - throw new FileNotFoundException(params.getPath()); - } - catch (AVMNotFoundException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch (AVMWrongTypeException ex) - { - throw new FileNotFoundException(params.getPath()); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new FileNotFoundException(params.getPath()); - } - - // Return the file - - return netFile; - } - - /** - * 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[] buf, int bufPos, int siz, - long filePos) throws java.io.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 - - AVMNetworkFile avmFile = (AVMNetworkFile) file; - - if (avmFile.hasContentChannel() == false) - beginReadTransaction( sess); - - // Read the file - - int rdlen = file.readFile(buf, siz, bufPos, filePos); - - // If we have reached end of file return a zero length read - - if (rdlen == -1) - rdlen = 0; - - // Return the actual read length - - return rdlen; - } - - /** - * 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 java.io.IOException - { - // Split the relative paths into parent and file/folder name pairs - - AVMContext ctx = (AVMContext) tree.getContext(); - - final String[] oldPaths = FileName.splitPath(oldName); - final String[] newPaths = FileName.splitPath(newName); - - // Convert the parent paths to store paths - - final AVMPath oldAVMPath = buildStorePath(ctx, oldPaths[0], sess); - final AVMPath newAVMPath = buildStorePath(ctx, newPaths[0], sess); - - // DEBUG - - if (logger.isDebugEnabled()) - { - logger.debug("Rename from path=" + oldPaths[0] + ", name=" + oldPaths[1]); - logger.debug(" new path=" + newPaths[0] + ", name=" + newPaths[1]); - } - - // Check if the filesystem is the virtualization view - - if (ctx.isVirtualizationView()) - { - if ( oldAVMPath.isReadOnlyPseudoPath()) - throw new AccessDeniedException("Cannot rename folder in store/version layer, " + oldName); - else if ( newAVMPath.isReadOnlyPseudoPath()) - throw new AccessDeniedException("Cannot rename folder to store/version layer, " + newName); - else if ( oldAVMPath.isReadOnlyAccess() ) - throw new AccessDeniedException("Cannot rename read-only folder, " + oldName); - else if ( newAVMPath.isReadOnlyAccess() ) - throw new AccessDeniedException("Cannot rename folder to read-only folder, " + newName); - } - - try - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - // Rename the file/folder - - m_avmService.rename(oldAVMPath.getAVMPath(), oldPaths[1], newAVMPath.getAVMPath(), newPaths[1]); - return null; - }}); - } - catch (AVMNotFoundException ex) - { - throw new IOException("Source not found, " + oldName); - } - catch (AVMWrongTypeException ex) - { - throw new IOException("Invalid path, " + oldName); - } - catch (AVMExistsException ex) - { - throw new FileExistsException("Destination exists, " + newName); - } - catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - throw new AccessDeniedException("Access denied, " + oldName); - } - } - - /** - * 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 java.io.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 - - AVMNetworkFile avmFile = (AVMNetworkFile) file; - - if (avmFile.hasContentChannel() == false) - beginReadTransaction( sess); - - // Set the file position - - return file.seekFile(pos, typ); - } - - /** - * Set the file information for the specified file. - * - * @param sess - * Server session - * @param tree - * Tree connection - * @param name - * java.lang.String - * @param info - * FileInfo - * @exception java.io.IOException - * The exception description. - */ - public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) - throws java.io.IOException - { - // Check if the file is being marked for deletion, check if the file is writable - - if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) - { - // If this is not the head version then it's not writable - - AVMContext avmCtx = (AVMContext) tree.getContext(); - - // Parse the path - - AVMPath storePath = buildStorePath(avmCtx, name, sess); - - if (avmCtx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess()) - throw new AccessDeniedException("Store not writable, cannot set delete on close"); - } - } - - /** - * 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 attrib) - throws java.io.FileNotFoundException - { - // Access the AVM context - - AVMContext avmCtx = (AVMContext) tree.getContext(); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Start search path=" + searchPath); - - // Split the search path into relative path and search name - - String[] paths = FileName.splitPath(searchPath); - - // Build the store path to the folder being searched - - AVMPath storePath = null; - - try - { - storePath = buildStorePath(avmCtx, paths[0], sess); - } - catch ( AccessDeniedException ex) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Start search access denied"); - - throw new FileNotFoundException("Access denied"); - } - - // Check if the filesystem is the virtualization view - - if (avmCtx.isVirtualizationView()) - { - // Check for a search of a pseudo folder - - if (storePath.isReadOnlyPseudoPath()) - { - // Get the file state for the folder being searched - - FileState fstate = findPseudoState(storePath, avmCtx); - - if (fstate != null) - { - // Get the pseudo file list for the parent directory - - PseudoFileList searchList = null; - - if ( storePath.isLevel() == AVMPath.LevelId.Root) - searchList = filterPseudoFolders(avmCtx, sess, storePath, fstate); - else - searchList = fstate.getPseudoFileList(); - - // Check if the pseudo file list is valid - - if (searchList == null) - searchList = new PseudoFileList(); - - // Check if this is a single file or wildcard search - - if (WildCard.containsWildcards(searchPath)) - { - // Create the search context, wildcard filter will take care of secondary filtering of the - // folder listing - - WildCard wildCardFilter = new WildCard(paths[1], false); - return new PseudoFileListSearchContext(searchList, attrib, wildCardFilter, storePath.isReadOnlyAccess()); - } - else - { - // Search the pseudo file list for the required file - - PseudoFile pseudoFile = searchList.findFile(paths[1], false); - if (pseudoFile != null) - { - // Create a search context using the single file details - - PseudoFileList singleList = new PseudoFileList(); - singleList.addFile(pseudoFile); - - return new PseudoFileListSearchContext(singleList, attrib, null, storePath.isReadOnlyAccess()); - } - } - } - - // File not found - - throw new FileNotFoundException(searchPath); - } - else if (storePath.isLevel() == AVMPath.LevelId.HeadMetaData - || storePath.isLevel() == AVMPath.LevelId.VersionMetaData) - { - // Return an empty file list for now - - PseudoFileList metaFiles = new PseudoFileList(); - - return new PseudoFileListSearchContext(metaFiles, attrib, null, storePath.isReadOnlyAccess()); - } - } - - // Check if the path is a wildcard search - - beginReadTransaction( sess); - SearchContext context = null; - - if (WildCard.containsWildcards(searchPath)) - { - // Get the file listing for the folder - - AVMNodeDescriptor[] fileList = m_avmService.getDirectoryListingArray(storePath.getVersion(), storePath.getAVMPath(), false); - - // Create the search context - - if (fileList != null) - { - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug(" Wildcard search returned " + fileList.length + " files"); - - // Create the search context, wildcard filter will take care of secondary filtering of the - // folder listing - - WildCard wildCardFilter = new WildCard(paths[1], false); - context = new AVMSearchContext(fileList, attrib, wildCardFilter, storePath.getRelativePath(), storePath.isReadOnlyAccess()); - } - } - else - { - // Single file/folder search, convert the path to a store path - - try - { - storePath = buildStorePath(avmCtx, searchPath, sess); - } - catch ( AccessDeniedException ex) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Start search access denied"); - - throw new FileNotFoundException("Access denied"); - } - - // Get the single file/folder details - - AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); - - if (nodeDesc != null) - { - // Create the search context for the single file/folder - - context = new AVMSingleFileSearchContext(nodeDesc, storePath.getRelativePath(), storePath.isReadOnlyAccess()); - } - - } - - // Return the search context - - return context; - } - - /** - * Truncate a file to the specified size - * - * @param sess - * Server session - * @param tree - * Tree connection - * @param file - * Network file details - * @param siz - * New file length - * @exception java.io.IOException - * The exception description. - */ - public void truncateFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final long siz) - throws java.io.IOException - { - // Check if the file is a directory, or only has read access - - if (file.getGrantedAccess() <= NetworkFile.READONLY) - throw new AccessDeniedException(); - - // If the content channel is not open for the file then start a transaction - - AVMNetworkFile avmFile = (AVMNetworkFile) file; - - // Truncate or extend the file - if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false) - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - file.truncateFile(siz); - file.flushFile(); - return null; - }}); - } - else - { - file.truncateFile(siz); - file.flushFile(); - } - - - } - - /** - * 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, final NetworkFile file, final byte[] buf, final int bufoff, final int siz, - final long fileoff) throws java.io.IOException - { - // Check if the file is a directory, or only has read access - - if (file.isDirectory() || file.getGrantedAccess() <= NetworkFile.READONLY) - throw new AccessDeniedException(); - - // If the content channel is not open for the file, or the channel is not writable, then start a transaction - - AVMNetworkFile avmFile = (AVMNetworkFile) file; - - // Write the data to the file - if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false) - { - doInWriteTransaction(sess, new CallableIO(){ - - public Void call() throws IOException - { - file.writeFile(buf, siz, bufoff, fileoff); - return null; - }}); - } - else - { - file.writeFile(buf, siz, bufoff, fileoff); - } - - - // Return the actual write length - - return siz; - } - - /** - * 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 - } - - /** - * Find the pseudo file for a virtual path - * - * @param avmPath - * AVMPath - * @param avmCtx - * AVMContext - * @return PseudoFile - */ - private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx) - { - return findPseudoFolder(avmPath, avmCtx, true); - } - - /** - * Find the pseudo file for a virtual path - * - * @param avmPath - * AVMPath - * @param avmCtx - * AVMContext - * @param generateStates - * boolean - * @return PseudoFile - */ - private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx, boolean generateStates) - { - // Check if the path is to a store pseudo folder - - if (avmPath.isRootPath()) - return null; - - // Get the file state for the parent of the required folder - - FileState fstate = null; - StringBuilder str = null; - PseudoFile psFile = null; - - switch (avmPath.isLevel()) - { - // Store root folder - - case StoreRoot: - - // Get the root folder file state - - fstate = avmCtx.getStateCache().findFileState(FileName.DOS_SEPERATOR_STR); - - if (fstate != null && fstate.hasPseudoFiles()) - psFile = fstate.getPseudoFileList().findFile(avmPath.getStoreName(), false); - break; - - // Versions root or Head folder - - case VersionRoot: - case Head: - - // Create a path to the parent store - - str = new StringBuilder(); - - str.append(FileName.DOS_SEPERATOR); - str.append(avmPath.getStoreName()); - - // Find/create the file state for the store - - AVMPath storePath = new AVMPath(str.toString()); - fstate = findPseudoState(storePath, avmCtx); - - // Find the version root or head pseudo folder - - if (fstate != null) - { - if (avmPath.isLevel() == AVMPath.LevelId.Head) - psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionNameHead, true); - else - psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionsFolder, true); - } - break; - - // Version folder - - case Version: - - // Create a path to the versions folder - - str = new StringBuilder(); - - str.append(FileName.DOS_SEPERATOR); - str.append(avmPath.getStoreName()); - str.append(FileName.DOS_SEPERATOR); - str.append(AVMPath.VersionsFolder); - - // Find/create the file state for the store - - AVMPath verrootPath = new AVMPath(str.toString()); - fstate = findPseudoState(verrootPath, avmCtx); - - // Find the version pseudo file - - if (fstate != null) - { - // Build the version folder name string - - str.setLength(0); - - str.append(AVMPath.VersionFolderPrefix); - str.append(avmPath.getVersion()); - - // find the version folder pseduo file - - psFile = fstate.getPseudoFileList().findFile(str.toString(), true); - } - break; - - // Head data or metadata folder - - case HeadData: - case HeadMetaData: - - // Create a path to the head folder - - str = new StringBuilder(); - - str.append(FileName.DOS_SEPERATOR); - str.append(avmPath.getStoreName()); - str.append(FileName.DOS_SEPERATOR); - str.append(AVMPath.VersionNameHead); - - // Find/create the file state for the store - - AVMPath headPath = new AVMPath(str.toString()); - fstate = findPseudoState(headPath, avmCtx); - - // Find the data or metadata pseudo folder - - if (fstate != null) - { - // Find the pseudo folder - - if (avmPath.isLevel() == AVMPath.LevelId.HeadData) - { - psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true); - } - else - { - psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true); - } - } - break; - - // Version data or metadata folder - - case VersionData: - case VersionMetaData: - - // Create a path to the version folder - - str = new StringBuilder(); - - str.append(FileName.DOS_SEPERATOR); - str.append(avmPath.getStoreName()); - str.append(FileName.DOS_SEPERATOR); - str.append(AVMPath.VersionFolderPrefix); - str.append(avmPath.getVersion()); - - // Find/create the file state for the store - - AVMPath verPath = new AVMPath(str.toString()); - fstate = findPseudoState(verPath, avmCtx); - - // Find the data or metadata pseudo folder - - if (fstate != null) - { - // Find the pseudo folder - - if (avmPath.isLevel() == AVMPath.LevelId.VersionData) - { - psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true); - } - else - { - psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true); - } - } - break; - } - - // Check if the pseudo file was not found but file states should be generated - - if (psFile == null && generateStates == true) - { - // Generate the file states for the path, this is required if a request is made to a path without - // walking the folder tree - - generatePseudoFolders(avmPath, avmCtx); - - // Try and find the pseudo file again - - psFile = findPseudoFolder(avmPath, avmCtx, false); - } - - // Return the pseudo file, or null if not found - - return psFile; - } - - /** - * Find the file state for a pseudo folder path - * - * @param avmPath - * AVMPath - * @param avmCtx - * AVMContext - * @return FileState - */ - protected final FileState findPseudoState(AVMPath avmPath, AVMContext avmCtx) - { - // Make sure the is to a pseudo file/folder - - if ( avmPath.isPseudoPath() == false) - return null; - - // Check if there are any new stores to be added to the virtualization view - - if ( avmCtx.hasNewStoresQueued()) { - - // Get the new stores list, there is a chance another thread might get the queue, if the queue is empty - // another thread is processing it - - StringList storeNames = avmCtx.getNewStoresQueue(); - - while ( storeNames.numberOfStrings() > 0) { - - // Get the current store name - - String curStoreName = storeNames.removeStringAt( 0); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Adding new store " + curStoreName); - - // Add the current store to the virtualization view - - addNewStore( avmCtx, curStoreName); - } - - // Get the root folder file state, update the modification timestamp - - FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); - if ( rootState != null) - rootState.updateModifyDateTime(); - } - - // Check if the path is to a store pseudo folder - - FileState fstate = null; - StringBuilder str = null; - String relPath = null; - - switch ( avmPath.isLevel()) - { - // Root of the hieararchy - - case Root: - - // Get the root path file state - - fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); - - // Check if the root file state is valid - - if ( fstate == null) - { - // Create a file state for the root folder - - fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true); - fstate.setExpiryTime( FileState.NoTimeout); - fstate.setFileStatus( DirectoryExists); - - // Set the modification timestamp for the root folder - - fstate.updateModifyDateTime(); - - // Get a list of the available AVM stores - - List storeList = m_avmService.getStores(); - - if ( storeList != null && storeList.size() > 0) - { - // Add pseudo files for the stores - - for ( AVMStoreDescriptor storeDesc : storeList) - { - // Get the properties for the current store - - String storeName = storeDesc.getName(); - Map props = m_avmService.getStoreProperties( storeName); - - // Check if the store is a main web project - - if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN)) - { - // Get the noderef for the web project - - PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF); - if ( prop != null) { - - // Get the web project noderef - - NodeRef webNodeRef = new NodeRef( prop.getStringValue()); - - if (m_nodeService.exists(webNodeRef)) - { - // Create the web project pseudo folder - - WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef); - fstate.addPseudoFile( webProjFolder); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( "Found web project " + webProjFolder.getFileName()); - - // Get the list of content managers for this web project - - List mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); - - for ( ChildAssociationRef mgrRef : mgrAssocs) - { - // Get the child node and see if it is a content manager association - - NodeRef childRef = mgrRef.getChildRef(); - - if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER)) - { - // Get the user name add it to the web project pseudo folder - - String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME); - - webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug(" Added content manager " + userName); - } - } - } - else - { - logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef); - } - } - } - else - { - // Check if this store is a web project sandbox - - int storeType = StoreType.Normal; - String webProjName = null; - String userName = null; - - if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) - { - // Sandbox store, linked to a web project - - storeType = StoreType.WebAuthorMain; - - // Get the associated web project name - - webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue(); - - // Get the user name from the store name - - userName = storeName.substring( webProjName.length() + 2); - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW)) - { - // Author preview sandbox store, linked to a web project - - storeType = StoreType.WebAuthorPreview; - - // Get the associated web project name - - String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length()); - int pos = projPlusUser.lastIndexOf("--"); - if ( pos != -1) - { - webProjName = projPlusUser.substring( 0, pos); - userName = projPlusUser.substring(pos + 2); - } - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW)) - { - // Staging preview sandbox store, linked to a web project - - storeType = StoreType.WebStagingPreview; - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW)) - { - // Staging preview sandbox store, linked to a web project - - storeType = StoreType.WebStagingPreview; - - // Get the associated web project name - - webProjName = storeName.substring( 0, storeName.length() - "--preview".length()); - } - else if ( props.containsKey(QName.createQName(null, ".sitestore"))) - { - // Site data store type - - storeType = StoreType.SiteStore; - } - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( "Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName); - - // Add a pseudo file for the current store - - if ( avmCtx.showStoreType( storeType)) - { - // Create the pseudo folder for the store - - StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType); - if (storeType == StoreType.WebAuthorMain || storeType == StoreType.WebAuthorPreview || - storeType == StoreType.WebStagingMain || storeType == StoreType.WebStagingPreview) - { - storeFolder.setWebProject( webProjName); - storeFolder.setUserName( userName); - } - - // Add the store pseudo folder to the root folder file list - - fstate.addPseudoFile( storeFolder); - } - } - } - } - - // Scan the pseudo folder list and add all publisher/reviewer user names to the web project roles list - - PseudoFileList folderList = fstate.getPseudoFileList(); - if ( folderList != null && folderList.numberOfFiles() > 0) - { - // Scan the pseudo folder list - - for ( int i = 0; i < folderList.numberOfFiles(); i++) - { - // Check if the current pseduo file is a store folder - - if ( folderList.getFileAt( i) instanceof StorePseudoFile) - { - // Check if the store has an associated web project - - StorePseudoFile curFile = (StorePseudoFile) folderList.getFileAt( i); - if ( curFile.hasWebProject()) - { - // Find the associated web project pseudo folder - - WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( curFile.getWebProject(), true); - - if (webProj == null) - { - logger.warn("Missing web project for: "+curFile.getFileName()+" ("+curFile.getWebProject()+")"); - } - else - { - // Strip the web project name from the sandbox store name and extract the user name. - // Add the user as a publisher/reviewer to the web project roles list - - String userName = curFile.getFileName().substring( webProj.getFileName().length() + 2); - - // If the user does not have a content manager role then add as a publisher - - if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone) - { - webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( "Added publisher " + userName + " to " + webProj.getFileName()); - } - } - } - } - } - } - } - break; - - // Store folder - - case StoreRoot: - - // Build the path to the parent store folder - - str = new StringBuilder(); - - str.append( FileName.DOS_SEPERATOR); - str.append( avmPath.getStoreName()); - - // Search for the file state for the store pseudo folder - - relPath = str.toString(); - fstate = avmCtx.getStateCache().findFileState( relPath); - - if ( fstate == null) - { - // Create a file state for the store path - - fstate = avmCtx.getStateCache().findFileState( str.toString(), true); - fstate.setFileStatus( DirectoryExists); - - // Add a pseudo file for the head version - - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.VersionNameHead); - - fstate.addPseudoFile( new VersionPseudoFile( AVMPath.VersionNameHead, str.toString())); - - // Add a pseudo file for the version root folder - - str.setLength( relPath.length() + 1); - str.append( AVMPath.VersionsFolder); - - fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.VersionsFolder, str.toString())); - } - break; - - // Head folder - - case Head: - - // Build the path to the store head version folder - - str = new StringBuilder(); - - str.append( FileName.DOS_SEPERATOR); - str.append( avmPath.getStoreName()); - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.VersionNameHead); - - // Search for the file state for the store head version pseudo folder - - relPath = str.toString(); - - fstate = avmCtx.getStateCache().findFileState( relPath); - - if ( fstate == null) - { - // Create a file state for the store head folder path - - fstate = avmCtx.getStateCache().findFileState( str.toString(), true); - fstate.setFileStatus( DirectoryExists); - - // Add a pseudo file for the data pseudo folder - - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.DataFolder); - - fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); - - // Add a pseudo file for the metadata pseudo folder - - str.setLength( relPath.length() + 1); - str.append( AVMPath.MetaDataFolder); - - fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); - } - break; - - // Version root folder - - case VersionRoot: - - // Get the list of AVM store versions - - try - { - // Build the path to the parent store folder - - str = new StringBuilder(); - - str.append( FileName.DOS_SEPERATOR); - str.append( avmPath.getStoreName()); - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.VersionsFolder); - - // Create a file state for the store path - - relPath = str.toString(); - fstate = avmCtx.getStateCache().findFileState( relPath, true); - fstate.setFileStatus( DirectoryExists); - - // Add pseudo folders if the list is empty - - if ( fstate.hasPseudoFiles() == false) - { - // Build the version folder name for the head version - - StringBuilder verStr = new StringBuilder( AVMPath.VersionFolderPrefix); - verStr.append( "-1"); - - // Add a pseudo file for the head version - - str.append( FileName.DOS_SEPERATOR); - str.append( verStr.toString()); - - fstate.addPseudoFile( new VersionPseudoFile( verStr.toString(), str.toString())); - - // Get the list of versions for the store - - List verList = m_avmService.getStoreVersions( avmPath.getStoreName()); - - // Add pseudo files for the versions to the store state - - if ( verList.size() > 0) - { - for ( VersionDescriptor verDesc : verList) - { - // Generate the version string - - String verName = null; - - verStr.setLength( AVMPath.VersionFolderPrefix.length()); - verStr.append( verDesc.getVersionID()); - - verName = verStr.toString(); - - str.setLength( relPath.length() + 1); - str.append( verName); - - // Add the version pseudo folder - - fstate.addPseudoFile( new VersionPseudoFile ( verName, verDesc, str.toString())); - } - } - } - } - catch ( AVMNotFoundException ex) - { - // Invalid store name - } - break; - - // Version folder - - case Version: - - // Build the path to the store version folder - - str = new StringBuilder(); - - str.append( FileName.DOS_SEPERATOR); - str.append( avmPath.getStoreName()); - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.VersionFolderPrefix); - str.append( avmPath.getVersion()); - - // Search for the file state for the version pseudo folder - - relPath = str.toString(); - fstate = avmCtx.getStateCache().findFileState( relPath); - - if ( fstate == null) - { - // Create a file state for the version folder path - - fstate = avmCtx.getStateCache().findFileState( str.toString(), true); - fstate.setFileStatus( DirectoryExists); - - // Add a pseudo file for the data pseudo folder - - str.append( FileName.DOS_SEPERATOR); - str.append( AVMPath.DataFolder); - - fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); - - // Add a pseudo file for the metadata pseudo folder - - str.setLength( relPath.length() + 1); - str.append( AVMPath.MetaDataFolder); - - fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); - } - break; - } - - // Return the file state - - return fstate; - } - - /** - * Generate the pseudo folders for the specified path - * - * @param avmPath - * AVMPath - * @param avmCtx - * AVMContext - */ - private final void generatePseudoFolders(AVMPath avmPath, AVMContext avmCtx) - { - // Create the root file state - - AVMPath createPath = new AVMPath(); - StringBuilder pathStr = new StringBuilder(); - - pathStr.append(FileName.DOS_SEPERATOR); - createPath.parsePath(pathStr.toString()); - - FileState rootState = findPseudoState(createPath, avmCtx); - - // Check if the path has a store name - - if (avmPath.getStoreName() != null) - { - // Check if the store name is valid - - if (rootState.hasPseudoFiles() - && rootState.getPseudoFileList().findFile(avmPath.getStoreName(), false) != null) - { - // Create the store file state - - pathStr.append(avmPath.getStoreName()); - pathStr.append(FileName.DOS_SEPERATOR); - - createPath.parsePath(pathStr.toString()); - - findPseudoState(createPath, avmCtx); - - // Add the head and version root pseudo folders - - createPath.parsePath(pathStr.toString() + AVMPath.VersionNameHead); - findPseudoState(createPath, avmCtx); - - createPath.parsePath(pathStr.toString() + AVMPath.VersionsFolder); - findPseudoState(createPath, avmCtx); - - // Check if the path is to a version folder - - if (avmPath.isLevel().ordinal() >= AVMPath.LevelId.Version.ordinal()) - { - // Build the path - - pathStr.append(AVMPath.VersionsFolder); - pathStr.append(FileName.DOS_SEPERATOR); - pathStr.append(AVMPath.VersionFolderPrefix); - pathStr.append(avmPath.getVersion()); - - createPath.parsePath(pathStr.toString()); - - // Generate the version folders - - findPseudoState(createPath, avmCtx); - } - } - } - } - - /** - * Check that the user has access to the path - * - * @param avmPath AVMPath - * @param avmCtx AVMContext - * @param sess SrvSession - * @exception AccessDeniedException - */ - private final void checkPathAccess( AVMPath avmPath, AVMContext avmCtx, SrvSession sess) - throws AccessDeniedException { - - // Only enforce access checks on virtualization views - - if ( avmCtx.isVirtualizationView() == false) - return; - - // Get the client details for the session - - ClientInfo cInfo = sess.getClientInformation(); - if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0) - throw new AccessDeniedException(); - - // Allow access to the root folder - - if ( avmPath.isLevel() == AVMPath.LevelId.Root || avmPath.isLevel() == AVMPath.LevelId.HeadData || avmPath.isLevel() == AVMPath.LevelId.StoreRootPath ) { - - // Allow read only access to the root, www and avm_webapps folders - avmPath.setReadOnlyAccess(true); - return; - } - - // Get root file state, get the store pseudo folder details - - FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); - if ( rootState == null){ - - // Recreate the root file state, new stores may have been added - - rootState = findPseudoState( new AVMPath( FileName.DOS_SEPERATOR_STR), avmCtx); - } - - // Check if there are any store pseudo folders - - if ( rootState != null && rootState.hasPseudoFiles()) - { - PseudoFile pseudoFolder = rootState.getPseudoFileList().findFile( avmPath.getStoreName(), false); - if ( pseudoFolder != null) - { - // Check if the pseudo folder is a web project folder or sandbox within a web project - - String curUserName = m_authComponent.getCurrentUserName(); - - if ( pseudoFolder instanceof WebProjectStorePseudoFile) - { - // Check the users role within the web project - - WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder; - - int role = webFolder.getUserRole( curUserName); - - if ( role == WebProjectStorePseudoFile.RoleNone) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("User " + curUserName + " has no access to web project, " + webFolder.getFileName()); - - // User does not have access to this web project - - throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName()); - } - else if ( avmCtx.allowAdminStagingWrites() && cInfo.isAdministrator()) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("User " + curUserName + " granted write access to web project, " + webFolder.getFileName()); - - // Allow admin write access - - avmPath.setReadOnlyAccess( false); - } - else - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("User " + curUserName + " granted read-only access to web project, " + webFolder.getFileName()); - - // Only allow read-only access to the staging area - - avmPath.setReadOnlyAccess( true); - } - } - else if ( pseudoFolder instanceof StorePseudoFile) - { - // Check the store type - - StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder; - if ( storeFolder.isStoreType() == StoreType.Normal) - return; - else if ( storeFolder.hasWebProject()) - { - // Get the web project that the sandbox is linked to - - WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) rootState.getPseudoFileList().findFile( storeFolder.getWebProject(), false); - - int role = webFolder.getUserRole( curUserName); - - if ( role == WebProjectStorePseudoFile.RoleNone) - { - // User does not have access to this web project - - throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName()); - } - else if ( role == WebProjectStorePseudoFile.RolePublisher && - storeFolder.getUserName().equalsIgnoreCase( curUserName) == false) - { - // User does not have access to this web project - - throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName()); - } - } - } - } - } - else - { - // Store does not exist - - throw new AccessDeniedException("Store does not exist, " + avmPath.getStoreName()); - } - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug( "Check access " + avmPath); - } - - /** - * Filter the list of pseudo folders returned in a search - * - * @param avmCtx AVMContext - * @param sess SrvSession - * @param avmPath AVMPath - * @param fstate FileState - * @return PseudoFileList - */ - private final PseudoFileList filterPseudoFolders( AVMContext avmCtx, SrvSession sess, AVMPath avmPath, FileState fstate) - { - // Check if the root folder file state has any store pseudo folders - - if ( fstate.hasPseudoFiles() == false) - return null; - - // Get the client details for the session - - ClientInfo cInfo = sess.getClientInformation(); - if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0) - return null; - - // Check for the admin user, no need to filter the list - - PseudoFileList fullList = fstate.getPseudoFileList(); - if ( cInfo.isAdministrator()) - return fullList; - - // Create a filtered list of store pseudo folders that the user has access to - - PseudoFileList filterList = new PseudoFileList(); - String userName = m_authComponent.getCurrentUserName(); - - for ( int i = 0; i < fullList.numberOfFiles(); i++) - { - // Get the current store pseudo folder - - PseudoFile pseudoFolder = fullList.getFileAt( i); - - // Check if the pseudo folder is a web project folder or sandbox within a web project - - if ( pseudoFolder instanceof WebProjectStorePseudoFile) - { - // Check the users role within the web project - - WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder; - - if ( avmCtx.showStagingStores() && webFolder.getUserRole( userName) != WebProjectStorePseudoFile.RoleNone) - { - // User has access to this store - - filterList.addFile( pseudoFolder); - } - } - else if ( pseudoFolder instanceof StorePseudoFile) - { - // Check if the store type should be included in the visible list - - StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder; - if ( avmCtx.showStoreType( storeFolder.isStoreType())) - { - // Check if the user has access to this store - - if ( storeFolder.hasWebProject()) - { - // Get the web project that the sandbox is linked to - - WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) fullList.findFile( storeFolder.getWebProject(), false); - - if ( webFolder != null) { - int role = webFolder.getUserRole( userName); - - if ( role == WebProjectStorePseudoFile.RoleContentManager && avmCtx.showStoreType( storeFolder.isStoreType())) - { - // User is a content manager, allow access to the store - - filterList.addFile( storeFolder); - } - else if ( role == WebProjectStorePseudoFile.RolePublisher && avmCtx.showStoreType( storeFolder.isStoreType())) - { - // Allow access if the user owns the current folder - - if ( storeFolder.getUserName().equalsIgnoreCase( userName)) - filterList.addFile( storeFolder); - } - } - else if ( logger.isDebugEnabled()) - logger.debug("Cannot find associated web folder for store " + storeFolder.getFileName()); - - } - else if ( avmCtx.showNormalStores() || avmCtx.showSiteStores()) - { - // Store is not linked to a web project, allow access to the store - - filterList.addFile( storeFolder); - } - } - } - } - - // Return the filtered list - - return filterList; - } - - /** - * Add a new store to the top level folder list - * - * @param avmCtx AVMContext - * @param storeName String - */ - protected void addNewStore( AVMContext avmCtx, String storeName) { - - // Get the root folder file state - - FileState fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true); - if ( fstate == null) - return; - - // Get the properties for the store - - AVMStoreDescriptor storeDesc = m_avmService.getStore( storeName); - if ( storeDesc == null) - return; - - Map props = m_avmService.getStoreProperties( storeName); - - // Check if the store is a main web project - - if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN)) - { - // Get the noderef for the web project - - PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF); - if ( prop != null) { - - // Get the web project noderef - - NodeRef webNodeRef = new NodeRef( prop.getStringValue()); - - if (m_nodeService.exists(webNodeRef)) - { - // Create the web project pseudo folder - - WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef); - fstate.addPseudoFile( webProjFolder); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( " Found web project " + webProjFolder.getFileName()); - - // Get the list of content managers for this web project - - List mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); - - for ( ChildAssociationRef mgrRef : mgrAssocs) - { - // Get the child node and see if it is a content manager association - - NodeRef childRef = mgrRef.getChildRef(); - - if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER)) - { - // Get the user name add it to the web project pseudo folder - - String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME); - - webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug(" Added content manager " + userName); - } - } - } - else - { - logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef); - } - } - } - else - { - // Check if this store is a web project sandbox - - int storeType = StoreType.Normal; - String webProjName = null; - String userName = null; - - if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) - { - // Sandbox store, linked to a web project - - storeType = StoreType.WebAuthorMain; - - // Get the associated web project name - - webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue(); - - // Get the user name from teh store name - - userName = storeName.substring( webProjName.length() + 2); - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW)) - { - // Author preview sandbox store, linked to a web project - - storeType = StoreType.WebAuthorPreview; - - // Get the associated web project name - - String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length()); - int pos = projPlusUser.lastIndexOf("--"); - if ( pos != -1) - { - webProjName = projPlusUser.substring( 0, pos); - userName = projPlusUser.substring(pos + 2); - } - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW)) - { - // Staging preview sandbox store, linked to a web project - - storeType = StoreType.WebStagingPreview; - } - else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW)) - { - // Staging preview sandbox store, linked to a web project - - storeType = StoreType.WebStagingPreview; - - // Get the associated web project name - - webProjName = storeName.substring( 0, storeName.length() - "--preview".length()); - } - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( " Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName); - - // Add a pseudo file for the current store - - if ( avmCtx.showStoreType( storeType)) - { - // Create the pseudo folder for the store - - StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType); - if ( storeType != StoreType.Normal) - { - storeFolder.setWebProject( webProjName); - storeFolder.setUserName( userName); - - // Add all publisher/reviewer user names to the web project roles list - - if ( storeFolder.hasWebProject()) - { - // Find the associated web project pseudo folder - - PseudoFileList folderList = fstate.getPseudoFileList(); - if ( folderList != null) { - - // Find the associated web project - - WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( storeFolder.getWebProject(), true); - - if ( webProj != null) { - - // Strip the web project name from the sandbox store name and extract the user name. - // Add the user as a publisher/reviewer to the web project roles list - - userName = storeFolder.getFileName().substring( webProj.getFileName().length() + 2); - - // If the user does not have a content manager role then add as a publisher - - if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone) - { - webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( " Added publisher " + userName + " to " + webProj.getFileName()); - } - } - } - } - } - - // Add the store pseudo folder to the root folder file list - - fstate.addPseudoFile( storeFolder); - } - } - } -} +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . */ + +package org.alfresco.filesys.avm; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.StringTokenizer; + +import javax.transaction.UserTransaction; + +import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.auth.ClientInfo; +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.DirectoryNotEmptyException; +import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileExistsException; +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.FileStatus; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.PathNotFoundException; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFolderNetworkFile; +import org.alfresco.jlan.util.StringList; +import org.alfresco.jlan.util.WildCard; +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.avm.CreateStoreTxnListener; +import org.alfresco.repo.avm.CreateVersionTxnListener; +import org.alfresco.repo.avm.PurgeStoreTxnListener; +import org.alfresco.repo.avm.PurgeVersionTxnListener; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.avm.AVMBadArgumentException; +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.AVMService; +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.avm.locking.AVMLockingException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.wcm.sandbox.SandboxConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * AVM Repository Filesystem Driver Class + *

+ * Provides a filesystem interface for various protocols such as SMB/CIFS and FTP. + * + * @author GKSpencer + */ +public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface +{ + // Logging + + private static final Log logger = LogFactory.getLog(AVMDiskDriver.class); + + // Configuration key names + + private static final String KEY_STORE = "storePath"; + private static final String KEY_VERSION = "version"; + private static final String KEY_CREATE = "createStore"; + + // AVM path seperator + + public static final char AVM_SEPERATOR = '/'; + public static final String AVM_SEPERATOR_STR = "/"; + + // Define client role names + + public static final String RoleContentManager = "ContentManager"; + public static final String RoleWebProject = "WebProject"; + public static final String RoleNotWebAuthor = "NotWebAuthor"; + + // Content manager web project role + + private static final String ROLE_CONTENT_MANAGER = "ContentManager"; + + // File status values used in the file state cache + + public static final int FileUnknown = FileStatus.Unknown; + public static final int FileNotExist = FileStatus.NotExist; + public static final int FileExists = FileStatus.FileExists; + public static final int DirectoryExists = FileStatus.DirectoryExists; + + public static final int CustomFileStatus= FileStatus.MaxStatus + 1; + + // Services and helpers + + private AVMService m_avmService; + private MimetypeService m_mimetypeService; + private AuthenticationComponent m_authComponent; + private AuthenticationService m_authService; + private NodeService m_nodeService; + + // AVM listeners + + private CreateStoreTxnListener m_createStoreListener; + private PurgeStoreTxnListener m_purgeStoreListener; + private CreateVersionTxnListener m_createVerListener; + private PurgeVersionTxnListener m_purgeVerListener; + + // Web project store + + private String m_webProjectStore; + + /** + * Default constructor + */ + public AVMDiskDriver() + { + } + + /** + * Return the AVM service + * + * @return AVMService + */ + public final AVMService getAvmService() + { + return m_avmService; + } + + /** + * Return the authentication service + * + * @return AuthenticationService + */ + public final AuthenticationService getAuthenticationService() + { + return m_authService; + } + + /** + * Set the AVM service + * + * @param avmService + * AVMService + */ + public void setAvmService(AVMService avmService) + { + m_avmService = avmService; + } + + /** + * Set the authentication component + * + * @param authComponent + * AuthenticationComponent + */ + public void setAuthenticationComponent(AuthenticationComponent authComponent) + { + m_authComponent = authComponent; + } + + /** + * Set the authentication service + * + * @param authService + * AuthenticationService + */ + public void setAuthenticationService(AuthenticationService authService) + { + m_authService = authService; + } + + /** + * Set the mimetype service + * + * @param mimetypeService + * MimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + m_mimetypeService = mimetypeService; + } + + /** + * Set the node service + * + * @param nodeService NodeService + */ + public void setNodeService(NodeService nodeService) + { + m_nodeService = nodeService; + } + + /** + * Set the create store listener + * + * @param createStoreListener + * CreateStoreTxnListener + */ + public void setCreateStoreListener(CreateStoreTxnListener createStoreListener) + { + m_createStoreListener = createStoreListener; + } + + /** + * Set the purge store listener + * + * @param purgeStoreListener + * PurgeStoreTxnListener + */ + public void setPurgeStoreListener(PurgeStoreTxnListener purgeStoreListener) + { + m_purgeStoreListener = purgeStoreListener; + } + + /** + * Set the create version listener + * + * @param createVersionListener + * CreateVersionTxnListener + */ + public void setCreateVersionListener(CreateVersionTxnListener createVersionListener) + { + m_createVerListener = createVersionListener; + } + + /** + * Set the purge version listener + * + * @param purgeVersionListener + * PurgeVersionTxnListener + */ + public void setPurgeVersionListener(PurgeVersionTxnListener purgeVersionListener) + { + m_purgeVerListener = purgeVersionListener; + } + + /** + * Set the web project store + * + * @param webStore String + */ + public void setWebProjectStore(String webStore) + { + m_webProjectStore = webStore; + } + + /** + * Parse and validate the parameter string and create a device context object for this instance of the shared + * device. + * + * @param shareName String + * @param cfg ConfigElement + * @return DeviceContext + * @exception DeviceContextException + */ + public DeviceContext createContext(String shareName, ConfigElement cfg) + throws DeviceContextException + { + AVMContext context = null; + + try + { + // Check if the share is a virtualization view + + ConfigElement virtElem = cfg.getChild("virtualView"); + if (virtElem != null) + { + // Check if virtualization view show options have been specified + + int showOptions = AVMContext.ShowStagingStores + AVMContext.ShowAuthorStores; + + String showAttr = virtElem.getAttribute( "stores"); + if ( showAttr != null) + { + // Split the show options string + + StringTokenizer tokens = new StringTokenizer( showAttr, ","); + StringList optList = new StringList(); + + while ( tokens.hasMoreTokens()) + optList.addString( tokens.nextToken().trim().toLowerCase()); + + // Build the show options mask + + showOptions = 0; + + if ( optList.containsString("normal")) + showOptions += AVMContext.ShowNormalStores; + + if ( optList.containsString("site")) + showOptions += AVMContext.ShowSiteStores; + + if ( optList.containsString("author")) + showOptions += AVMContext.ShowAuthorStores; + + if ( optList.containsString("preview")) + showOptions += AVMContext.ShowPreviewStores; + + if ( optList.containsString("staging")) + showOptions += AVMContext.ShowStagingStores; + } + else if ( cfg.getChild("showAllSandboxes") != null) + { + // Old style show options + + showOptions = AVMContext.ShowNormalStores + AVMContext.ShowSiteStores + + AVMContext.ShowAuthorStores + AVMContext.ShowPreviewStores + + AVMContext.ShowStagingStores; + } + + // Create the context + + context = new AVMContext(shareName, showOptions, this); + + // Check if the admin user should be allowed to write to the web project staging stores + + if ( cfg.getChild("adminWriteable") != null) + context.setAllowAdminStagingWrites( true); + + } + else + { + // Get the store path + + 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 storePath = storeElement.getValue(); + + // Get the version if specified, or default to the head version + + int version = AVMContext.VERSION_HEAD; + + ConfigElement versionElem = cfg.getChild(KEY_VERSION); + if (versionElem != null) + { + // Check if the version is valid + + if (versionElem.getValue() == null || versionElem.getValue().length() == 0) + throw new DeviceContextException("Store version not specified"); + + // Validate the version id + + try + { + version = Integer.parseInt(versionElem.getValue()); + } + catch (NumberFormatException ex) + { + throw new DeviceContextException("Invalid store version specified, " + + versionElem.getValue()); + } + + // Range check the version id + + if (version < 0 && version != AVMContext.VERSION_HEAD) + throw new DeviceContextException("Invalid store version id specified, " + version); + } + + // Create the context + + context = new AVMContext(shareName, storePath, version); + + // Check if the create flag is enabled + + ConfigElement createStore = cfg.getChild(KEY_CREATE); + context.setCreateStore(createStore != null); + + // Enable file state caching + + //context.enableStateCache( true); + } + + } + catch (Exception ex) + { + logger.error("Error during create context", ex); + + // Rethrow the exception + + throw new DeviceContextException("Driver setup error, " + ex.getMessage()); + } + + // Register the context bean + registerContext(context); + + // Return the context for this shared filesystem + return context; + } + + /** + * Register a device context object for this instance of the shared + * device. + * + * @param context the device context + * @param serverConfig ServerConfigurationBean + * @exception DeviceContextException + */ + @Override + public void registerContext(DeviceContext ctx) + throws DeviceContextException + { + super.registerContext(ctx); + + AVMContext context = (AVMContext)ctx; + // Use the system user as the authenticated context for the filesystem initialization + + try + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + // Wrap the initialization in a transaction + + UserTransaction tx = getTransactionService().getUserTransaction(false); + + try + { + // Start the transaction + + if (tx != null) + tx.begin(); + + // Check if the share is a virtualization view + + if (context.isVirtualizationView()) + { + // Enable file state caching + +// context.enableStateCache(serverConfig, true); + + // Plug the virtualization view context into the various store/version call back listeners + // so that store/version pseudo folders can be kept in sync with AVM + + m_createStoreListener.addCallback(context); + m_purgeStoreListener.addCallback(context); + + m_createVerListener.addCallback(context); + m_purgeVerListener.addCallback(context); + + // Create the file state for the root path, this will build the store pseudo folder list + + findPseudoState( new AVMPath( ""), context); + } + else + { + // Get the store path + String storePath = context.getStorePath(); + + // Get the version + int version = context.isVersion(); + + // Validate the store path + + AVMNodeDescriptor rootNode = m_avmService.lookup(version, storePath); + if (rootNode == null) + { + // Check if the store should be created + + if (!context.getCreateStore()|| version != AVMContext.VERSION_HEAD) + throw new DeviceContextException("Invalid store path/version, " + + storePath + " (" + version + ")"); + + // Parse the store path + + String storeName = null; + String path = null; + + int pos = storePath.indexOf(":/"); + if (pos != -1) + { + storeName = storePath.substring(0, pos); + if (storePath.length() > pos) + path = storePath.substring(pos + 2); + } + else + storeName = storePath; + + // Check if the store exists + + AVMStoreDescriptor storeDesc = null; + + try + { + storeDesc = m_avmService.getStore(storeName); + } + catch (AVMNotFoundException ex) + { + } + + // Create a new store if it does not exist + + if (storeDesc == null) + m_avmService.createStore(storeName); + + // Check if there is an optional path + + if (path != null) + { + // Split the path + + StringTokenizer tokens = new StringTokenizer(path, AVMPath.AVM_SEPERATOR_STR); + StringList paths = new StringList(); + + while (tokens.hasMoreTokens()) + paths.addString(tokens.nextToken()); + + // Create the path, or folders that do not exist + + AVMPath curPath = new AVMPath(storeName, version, FileName.DOS_SEPERATOR_STR); + AVMNodeDescriptor curDesc = m_avmService.lookup(curPath.getVersion(), curPath.getAVMPath()); + + // Walk the path checking creating each folder as required + + for (int i = 0; i < paths.numberOfStrings(); i++) + { + AVMNodeDescriptor nextDesc = null; + + try + { + // Check if the child folder exists + + nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i)); + } + catch (AVMNotFoundException ex) + { + } + + // Check if the folder exists + + if (nextDesc == null) + { + // Create the new folder + + m_avmService.createDirectory(curPath.getAVMPath(), paths.getStringAt(i)); + + // Get the details of the new folder + + nextDesc = m_avmService.lookup(curDesc, paths.getStringAt(i)); + } + else if (nextDesc.isFile()) + throw new DeviceContextException("Path element error, not a folder, " + + paths.getStringAt(i)); + + // Step to the next level + + curPath.parsePath(storeName, version, curPath.getRelativePath() + + paths.getStringAt(i) + FileName.DOS_SEPERATOR_STR); + curDesc = nextDesc; + } + } + + // Validate the store path again + + rootNode = m_avmService.lookup(version, storePath); + if (rootNode == null) + throw new DeviceContextException("Failed to create new store " + storePath); + } + + // Enable file state caching + +// context.enableStateCache(serverConfig, true); + } + + // Commit the transaction + + tx.commit(); + tx = null; + } + catch (Exception ex) + { + logger.error("Error during create context", ex); + + // Rethrow the exception + + throw new DeviceContextException("Driver setup error, " + ex.getMessage(), ex); + } + finally + { + // 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); + } + } + } + + // Return the context for this shared filesystem + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + + /** + * Return a list of the available AVM store names + * + * @return StringList + */ + public final StringList getAVMStoreNames() + { + // Use the system user as the authenticated context to get the AVM store list + + String currentUser = m_authComponent.getCurrentUserName(); + try + { + m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); + + // Wrap the service request in a transaction + + UserTransaction tx = getTransactionService().getUserTransaction(false); + + StringList storeNames = new StringList(); + + try + { + // Start the transaction + + if (tx != null) + tx.begin(); + + // Get the list of AVM stores + + List storeList = m_avmService.getStores(); + + if (storeList != null) + { + for (AVMStoreDescriptor storeDesc : storeList) + storeNames.addString(storeDesc.getName()); + } + + // Commit the transaction + if (tx != null) + tx.commit(); + tx = null; + } + catch (Exception ex) + { + logger.error("Error getting store names", ex); + } + finally + { + // 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); + } + } + } + + // Return the list of AVM store names + + return storeNames; + } + finally + { + m_authComponent.setCurrentUser(currentUser); + } + } + + /** + * Get the properties for a store + * + * @param storeName + * String + * @return Map + */ + protected final Map getAVMStoreProperties(String storeName) + { + // Use the system user as the authenticated context to get the AVM store properties + + String currentUser = m_authComponent.getCurrentUserName(); + try + { + m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); + + // Wrap the service request in a transaction + + UserTransaction tx = getTransactionService().getUserTransaction(false); + + Map properties = null; + + try + { + // Start the transaction + + if (tx != null) + tx.begin(); + + // Get the list of properties for AVM store + + properties = m_avmService.getStoreProperties(storeName); + + // Commit the transaction + + if (tx != null) + tx.commit(); + tx = null; + } + catch (Exception ex) + { + logger.error("Error getting store properties", ex); + } + finally + { + // 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); + } + } + } + + // Return the list of AVM store properties + + return properties; + } + finally + { + m_authComponent.setCurrentUser(currentUser); + } + } + + /** + * Build the full store path for a file/folder using the share relative path + * + * @param ctx AVMContext + * @param path String + * @param sess SrvSession + * @return AVMPath + * @exception AccessDeniedException + */ + protected final AVMPath buildStorePath(AVMContext ctx, String path, SrvSession sess) + throws AccessDeniedException + { + // Check if the AVM filesystem is a normal or virtualization view + + AVMPath avmPath = null; + + if (ctx.isVirtualizationView()) + { + // Create a path for the virtualization view + + avmPath = new AVMPath(path); + + // Check that the user has access to the path + + checkPathAccess( avmPath, ctx, sess); + } + else + { + // Create a path to a single store/version + + avmPath = new AVMPath(ctx.getStorePath(), ctx.isVersion(), path); + } + + // Return the path + + return avmPath; + } + + /** + * Close the file. + * + * @param sess + * Server session + * @param tree + * Tree connection. + * @param file + * Network file context. + * @exception java.io.IOException + * If an error occurs. + */ + public void closeFile(final SrvSession sess, final TreeConnection tree, final NetworkFile file) throws java.io.IOException + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Close file " + file.getFullName()); + + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + // Close the file + + file.closeFile(); + + // Check if the file/directory is marked for delete + + if (file.hasDeleteOnClose()) + { + + // Check for a file or directory + + if (file.isDirectory()) + deleteDirectory(sess, tree, file.getFullName()); + else + deleteFile(sess, tree, file.getFullName()); + } + return null; + }}); + } + + /** + * 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 java.io.IOException + { + // Check if the filesystem is writable + + AVMContext ctx = (AVMContext) tree.getContext(); + if (ctx.isVersion() != AVMContext.VERSION_HEAD) + throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable"); + + // Split the path to get the new folder name and relative path + + final String[] paths = FileName.splitPath(params.getPath()); + + // Convert the relative path to a store path + + final AVMPath storePath = buildStorePath(ctx, paths[0], sess); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]); + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView()) + { + if (storePath.isReadOnlyPseudoPath()) + throw new AccessDeniedException("Cannot create folder in store/version layer, " + params.getPath()); + else if ( storePath.isReadOnlyAccess()) + throw new AccessDeniedException("Cannot create folder " + params.getPath() + ", read-only path"); + } + + // Create a new file + + try + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + // Create the new file entry + + m_avmService.createDirectory(storePath.getAVMPath(), paths[1]); + + return null; + }}); + } + catch (AVMExistsException ex) + { + throw new FileExistsException(params.getPath()); + } + catch (AVMNotFoundException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMWrongTypeException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMBadArgumentException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMLockingException ex) + { + throw new AccessDeniedException(params.getPath()); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new AccessDeniedException(params.getPath()); + } + } + + /** + * 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(final SrvSession sess, TreeConnection tree, final FileOpenParams params) + throws java.io.IOException + { + // Check if the filesystem is writable + + final AVMContext ctx = (AVMContext) tree.getContext(); + + // Split the path to get the file name and relative path + + final String[] paths = FileName.splitPath(params.getPath()); + + // Convert the relative path to a store path + + final AVMPath storePath = buildStorePath(ctx, paths[0], sess); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]); + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView()) + { + if (storePath.isReadOnlyPseudoPath()) + throw new AccessDeniedException("Cannot create file in store/version layer, " + params.getPath()); + else if ( storePath.isReadOnlyAccess()) + throw new AccessDeniedException("Cannot create file " + params.getPath() + ", read-only path"); + } + else if (storePath.getVersion() != AVMContext.VERSION_HEAD) + { + throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable"); + } + + + try + { + // Create a new file + return doInWriteTransaction(sess, new CallableIO(){ + + public NetworkFile call() throws IOException + { + // Create the new file entry + + m_avmService.createFile(storePath.getAVMPath(), paths[1]).close(); + + // Get the new file details + + AVMPath fileStorePath = buildStorePath(ctx, params.getPath(), sess); + AVMNodeDescriptor nodeDesc = m_avmService.lookup(fileStorePath.getVersion(), fileStorePath.getAVMPath()); + + if (nodeDesc != null) + { + // Create the network file object for the new file + + AVMNetworkFile netFile = new AVMNetworkFile(nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(), + m_nodeService, m_avmService); + netFile.setGrantedAccess(NetworkFile.READWRITE); + netFile.setFullName(params.getPath()); + + netFile.setFileId(fileStorePath.generateFileId()); + + // Set the mime-type for the new file + + netFile.setMimeType(m_mimetypeService.guessMimetype(paths[1])); + return netFile; + } + return null; + }}); + } + catch (AVMExistsException ex) + { + throw new FileExistsException(params.getPath()); + } + catch (AVMNotFoundException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMWrongTypeException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMBadArgumentException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMLockingException ex) + { + throw new AccessDeniedException(params.getPath()); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new AccessDeniedException(params.getPath()); + } + } + + /** + * 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, final String dir) throws java.io.IOException + { + // Convert the relative path to a store path + + AVMContext ctx = (AVMContext) tree.getContext(); + + final String[] paths = FileName.splitPath(dir); + final AVMPath parentPath = buildStorePath(ctx, paths[0], sess); + final AVMPath dirPath = buildStorePath(ctx, dir, sess); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Delete directory, path=" + dir + ", dirPath=" + dirPath); + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView()) + { + if (parentPath.isPseudoPath()) + throw new AccessDeniedException("Cannot delete folder in store/version layer, " + dir); + else if ( parentPath.isReadOnlyAccess()) + throw new AccessDeniedException("Cannot delete folder " + dir + ", read-only path"); + } + + // Make sure the path is to a folder before deleting it + + try + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + AVMNodeDescriptor nodeDesc = m_avmService.lookup(dirPath.getVersion(), dirPath.getAVMPath()); + if (nodeDesc != null) + { + // Check that we are deleting a folder + + if (nodeDesc.isDirectory()) + { + // Make sure the directory is empty + + SortedMap fileList = m_avmService.getDirectoryListing(nodeDesc); + if (fileList != null && fileList.size() > 0) + throw new DirectoryNotEmptyException(dir); + + // Delete the folder + + m_avmService.removeNode(dirPath.getAVMPath()); + } + else + throw new IOException("Delete directory path is not a directory, " + dir); + } + return null; + }}); + } + catch (AVMNotFoundException ex) + { + throw new IOException("Directory not found, " + dir); + } + catch (AVMWrongTypeException ex) + { + throw new IOException("Invalid path, " + dir); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new AccessDeniedException("Access denied, " + dir); + } + } + + /** + * 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, final String name) throws java.io.IOException + { + // Convert the relative path to a store path + + AVMContext ctx = (AVMContext) tree.getContext(); + + final String[] paths = FileName.splitPath(name); + final AVMPath parentPath = buildStorePath(ctx, paths[0], sess); + final AVMPath filePath = buildStorePath(ctx, name, sess); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Delete file, path=" + name + ", filePath=" + filePath); + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView()) + { + if (parentPath.isPseudoPath()) + throw new AccessDeniedException("Cannot delete file in store/version layer, " + name); + else if ( parentPath.isReadOnlyAccess()) + throw new AccessDeniedException("Cannot delete file " + name + ", read-only path"); + } + + // Make sure the path is to a file before deleting it + + try + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + AVMNodeDescriptor nodeDesc = m_avmService.lookup(filePath.getVersion(), filePath.getAVMPath()); + if (nodeDesc != null) + { + // Check that we are deleting a file + + if (nodeDesc.isFile()) + { + // Delete the file + + m_avmService.removeNode(filePath.getAVMPath()); + } + else + throw new IOException("Delete file path is not a file, " + name); + } + return null; + }}); + } + catch (AVMNotFoundException ex) + { + throw new IOException("File not found, " + name); + } + catch (AVMWrongTypeException ex) + { + throw new IOException("Invalid path, " + name); + } + catch (AVMLockingException ex) + { + throw new AccessDeniedException("File locked, " + name); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new AccessDeniedException("Access denied, " + name); + } + } + + /** + * 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) + { + // Convert the relative path to a store path + + AVMContext ctx = (AVMContext) tree.getContext(); + AVMPath storePath = null; + + try + { + storePath = buildStorePath(ctx, name, sess); + } + catch ( AccessDeniedException ex) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("File exists check, path=" + name + " Access denied"); + + return FileStatus.NotExist; + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("File exists check, path=" + name + ", storePath=" + storePath); + + // Check if the path is valid + + int status = FileStatus.NotExist; + + if (storePath.isValid() == false) + return status; + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) + { + // Find the file state for the pseudo folder + + FileState fstate = findPseudoState(storePath, ctx); + + if (fstate != null) + { + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" Found pseudo file " + fstate); + + // Check if the pseudo file is a file or folder + + if (fstate.isDirectory()) + status = FileStatus.DirectoryExists; + else + status = FileStatus.FileExists; + } + else + { + // Invalid pseudo file path + + status = FileStatus.NotExist; + } + + // Return the file status + + return status; + } + + // Search for the file/folder + + beginReadTransaction( sess); + + AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); + + if (nodeDesc != null) + { + // Check if the path is to a file or folder + + if (nodeDesc.isDirectory()) + status = FileStatus.DirectoryExists; + else + status = FileStatus.FileExists; + } + + // Return the file status + + return status; + } + + /** + * 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 java.io.IOException + { + // Flush the file + + file.flushFile(); + } + + /** + * 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 sess, TreeConnection tree, String name) throws java.io.IOException + { + // Convert the relative path to a store path + + AVMContext ctx = (AVMContext) tree.getContext(); + AVMPath storePath = null; + + try + { + storePath = buildStorePath( ctx, name, sess); + } + catch ( Exception ex) + { + throw new FileNotFoundException( name); + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Get file information, path=" + name + ", storePath=" + storePath); + + // Check if hte path is valid + + if ( storePath.isValid() == false) + throw new FileNotFoundException( name); + + // Check if the filesystem is the virtualization view + + if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) + { + // Search for the pseudo path, to check for any new stores + + FileState fstate = findPseudoState( storePath, ctx); + + // Check if the search path is for the root, a store or version folder + + if ( storePath.isRootPath()) + { + // Return dummy file informatiom for the root folder, use cached timestamps + + FileInfo finfo = new FileInfo( name, 0L, FileAttribute.Directory); + + if ( fstate != null) { + finfo.setModifyDateTime( fstate.getModifyDateTime()); + finfo.setChangeDateTime( fstate.getModifyDateTime()); + } + + // Return the root folder file information + + return finfo; + } + else + { + // Find the pseudo file for the store/version folder + + PseudoFile psFile = findPseudoFolder( storePath, ctx); + if ( psFile != null) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( " Found pseudo file " + psFile); + return psFile.getFileInfo(); + } + else + throw new FileNotFoundException( name); + } + } + + // Search for the file/folder + + beginReadTransaction( sess); + + FileInfo info = null; + + try + { + AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath()); + + if ( nodeDesc != null) + { + // Create, and fill in, the file information + + info = new FileInfo(); + + info.setFileName( nodeDesc.getName()); + + if ( nodeDesc.isFile()) + { + info.setFileSize( nodeDesc.getLength()); + info.setAllocationSize((nodeDesc.getLength() + 512L) & 0xFFFFFFFFFFFFFE00L); + } + else + info.setFileSize( 0L); + + info.setAccessDateTime( nodeDesc.getAccessDate()); + info.setCreationDateTime( nodeDesc.getCreateDate()); + info.setModifyDateTime( nodeDesc.getModDate()); + info.setChangeDateTime( nodeDesc.getModDate()); + + // Build the file attributes + + int attr = 0; + + if ( nodeDesc.isDirectory()) + attr += FileAttribute.Directory; + + if ( nodeDesc.getName().startsWith( ".") || + nodeDesc.getName().equalsIgnoreCase( "Desktop.ini") || + nodeDesc.getName().equalsIgnoreCase( "Thumbs.db")) + attr += FileAttribute.Hidden; + + // Mark the file/folder as read-only if not the head version + + if ( ctx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess()) + attr += FileAttribute.ReadOnly; + + if ( attr == 0) + attr = FileAttribute.NTNormal; + + info.setFileAttributes( attr); + + // Set the file id + + info.setFileId( storePath.generateFileId()); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug(" File info=" + info); + } + } + catch ( AVMNotFoundException ex) + { + throw new FileNotFoundException( name); + } + catch ( AVMWrongTypeException ex) + { + throw new PathNotFoundException( name); + } + + // Return the file information + + return info; + } + + /** + * 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 java.io.IOException + { + // Check if the version indicates the head version, only the head is writable + + AVMContext avmCtx = (AVMContext) ctx; + return avmCtx.isVersion() == AVMContext.VERSION_HEAD ? true : false; + } + + /** + * Open a file on the file system. + * + * @param sess + * Server session + * @param tree + * Tree connection + * @param params + * File open parameters + * @return NetworkFile + * @exception java.io.IOException + * If an error occurs. + */ + public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws java.io.IOException + { + // Convert the relative path to a store path + + AVMContext ctx = (AVMContext) tree.getContext(); + AVMPath storePath = buildStorePath(ctx, params.getPath(), sess); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Open file params=" + params + ", storePath=" + storePath); + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath()) + { + // Check if the path is for the root, a store or version folder + + if (storePath.isRootPath()) + { + // Return a dummy file for the root folder + + return new PseudoFolderNetworkFile(FileName.DOS_SEPERATOR_STR); + } + else + { + // Find the pseudo file for the store/version folder + + PseudoFile psFile = findPseudoFolder(storePath, ctx); + if (psFile != null) + { + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" Found pseudo file " + psFile); + return psFile.getFile(params.getPath()); + } + else + return null; + } + } + + // Search for the file/folder + + beginReadTransaction( sess); + + AVMNetworkFile netFile = null; + + try + { + // Get the details of the file/folder + + AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); + + if (nodeDesc != null) + { + // Check if the filesystem is read-only and write access has been requested + + if (storePath.getVersion() != AVMContext.VERSION_HEAD + && (params.isReadWriteAccess() || params.isWriteOnlyAccess())) + throw new AccessDeniedException("File " + params.getPath() + " is read-only"); + + // Create the network file object for the opened file/folder + + netFile = new AVMNetworkFile(nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_nodeService, m_avmService); + + if (params.isReadOnlyAccess() || storePath.getVersion() != AVMContext.VERSION_HEAD) + netFile.setGrantedAccess(NetworkFile.READONLY); + else + netFile.setGrantedAccess(NetworkFile.READWRITE); + + netFile.setFullName(params.getPath()); + netFile.setFileId(storePath.generateFileId()); + + // Set the mime-type for the new file + + netFile.setMimeType(m_mimetypeService.guessMimetype(params.getPath())); + } + else + throw new FileNotFoundException(params.getPath()); + } + catch (AVMNotFoundException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch (AVMWrongTypeException ex) + { + throw new FileNotFoundException(params.getPath()); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new FileNotFoundException(params.getPath()); + } + + // Return the file + + return netFile; + } + + /** + * 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[] buf, int bufPos, int siz, + long filePos) throws java.io.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 + + AVMNetworkFile avmFile = (AVMNetworkFile) file; + + if (avmFile.hasContentChannel() == false) + beginReadTransaction( sess); + + // Read the file + + int rdlen = file.readFile(buf, siz, bufPos, filePos); + + // If we have reached end of file return a zero length read + + if (rdlen == -1) + rdlen = 0; + + // Return the actual read length + + return rdlen; + } + + /** + * 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 java.io.IOException + { + // Split the relative paths into parent and file/folder name pairs + + AVMContext ctx = (AVMContext) tree.getContext(); + + final String[] oldPaths = FileName.splitPath(oldName); + final String[] newPaths = FileName.splitPath(newName); + + // Convert the parent paths to store paths + + final AVMPath oldAVMPath = buildStorePath(ctx, oldPaths[0], sess); + final AVMPath newAVMPath = buildStorePath(ctx, newPaths[0], sess); + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug("Rename from path=" + oldPaths[0] + ", name=" + oldPaths[1]); + logger.debug(" new path=" + newPaths[0] + ", name=" + newPaths[1]); + } + + // Check if the filesystem is the virtualization view + + if (ctx.isVirtualizationView()) + { + if ( oldAVMPath.isReadOnlyPseudoPath()) + throw new AccessDeniedException("Cannot rename folder in store/version layer, " + oldName); + else if ( newAVMPath.isReadOnlyPseudoPath()) + throw new AccessDeniedException("Cannot rename folder to store/version layer, " + newName); + else if ( oldAVMPath.isReadOnlyAccess() ) + throw new AccessDeniedException("Cannot rename read-only folder, " + oldName); + else if ( newAVMPath.isReadOnlyAccess() ) + throw new AccessDeniedException("Cannot rename folder to read-only folder, " + newName); + } + final NodeRef oldNodeRef = AVMNodeConverter.ToNodeRef(-1, buildStorePath(ctx, oldName, sess).getAVMPath()); + final NodeRef newNodeParentRef = AVMNodeConverter.ToNodeRef(-1, newAVMPath.getAVMPath()); + try + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + // Rename the file/folder + + m_nodeService.moveNode(oldNodeRef, newNodeParentRef, ContentModel.ASSOC_CONTAINS, QName.createQName(newPaths[1])); + return null; + }}); + } + catch (InvalidNodeRefException ex) + { + throw new IOException("Node reference no longer exists"); + } + catch (CyclicChildRelationshipException ex) + { + throw new IOException("Cyclic parent-child relationship"); + } + catch (AVMWrongTypeException ex) + { + throw new IOException("Invalid path, " + oldName); + } + catch ( org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + throw new AccessDeniedException("Access denied, " + oldName); + } + } + + /** + * 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 java.io.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 + + AVMNetworkFile avmFile = (AVMNetworkFile) file; + + if (avmFile.hasContentChannel() == false) + beginReadTransaction( sess); + + // Set the file position + + return file.seekFile(pos, typ); + } + + /** + * Set the file information for the specified file. + * + * @param sess + * Server session + * @param tree + * Tree connection + * @param name + * java.lang.String + * @param info + * FileInfo + * @exception java.io.IOException + * The exception description. + */ + public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) + throws java.io.IOException + { + // Check if the file is being marked for deletion, check if the file is writable + + if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) + { + // If this is not the head version then it's not writable + + AVMContext avmCtx = (AVMContext) tree.getContext(); + + // Parse the path + + AVMPath storePath = buildStorePath(avmCtx, name, sess); + + if (avmCtx.isVersion() != AVMContext.VERSION_HEAD || storePath.isReadOnlyAccess()) + throw new AccessDeniedException("Store not writable, cannot set delete on close"); + } + } + + /** + * 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 attrib) + throws java.io.FileNotFoundException + { + // Access the AVM context + + AVMContext avmCtx = (AVMContext) tree.getContext(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Start search path=" + searchPath); + + // Split the search path into relative path and search name + + String[] paths = FileName.splitPath(searchPath); + + // Build the store path to the folder being searched + + AVMPath storePath = null; + + try + { + storePath = buildStorePath(avmCtx, paths[0], sess); + } + catch ( AccessDeniedException ex) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Start search access denied"); + + throw new FileNotFoundException("Access denied"); + } + + // Check if the filesystem is the virtualization view + + if (avmCtx.isVirtualizationView()) + { + // Check for a search of a pseudo folder + + if (storePath.isReadOnlyPseudoPath()) + { + // Get the file state for the folder being searched + + FileState fstate = findPseudoState(storePath, avmCtx); + + if (fstate != null) + { + // Get the pseudo file list for the parent directory + + PseudoFileList searchList = null; + + if ( storePath.isLevel() == AVMPath.LevelId.Root) + searchList = filterPseudoFolders(avmCtx, sess, storePath, fstate); + else + searchList = fstate.getPseudoFileList(); + + // Check if the pseudo file list is valid + + if (searchList == null) + searchList = new PseudoFileList(); + + // Check if this is a single file or wildcard search + + if (WildCard.containsWildcards(searchPath)) + { + // Create the search context, wildcard filter will take care of secondary filtering of the + // folder listing + + WildCard wildCardFilter = new WildCard(paths[1], false); + return new PseudoFileListSearchContext(searchList, attrib, wildCardFilter, storePath.isReadOnlyAccess()); + } + else + { + // Search the pseudo file list for the required file + + PseudoFile pseudoFile = searchList.findFile(paths[1], false); + if (pseudoFile != null) + { + // Create a search context using the single file details + + PseudoFileList singleList = new PseudoFileList(); + singleList.addFile(pseudoFile); + + return new PseudoFileListSearchContext(singleList, attrib, null, storePath.isReadOnlyAccess()); + } + } + } + + // File not found + + throw new FileNotFoundException(searchPath); + } + else if (storePath.isLevel() == AVMPath.LevelId.HeadMetaData + || storePath.isLevel() == AVMPath.LevelId.VersionMetaData) + { + // Return an empty file list for now + + PseudoFileList metaFiles = new PseudoFileList(); + + return new PseudoFileListSearchContext(metaFiles, attrib, null, storePath.isReadOnlyAccess()); + } + } + + // Check if the path is a wildcard search + + beginReadTransaction( sess); + SearchContext context = null; + + if (WildCard.containsWildcards(searchPath)) + { + // Get the file listing for the folder + + AVMNodeDescriptor[] fileList = m_avmService.getDirectoryListingArray(storePath.getVersion(), storePath.getAVMPath(), false); + + // Create the search context + + if (fileList != null) + { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" Wildcard search returned " + fileList.length + " files"); + + // Create the search context, wildcard filter will take care of secondary filtering of the + // folder listing + + WildCard wildCardFilter = new WildCard(paths[1], false); + context = new AVMSearchContext(fileList, attrib, wildCardFilter, storePath.getRelativePath(), storePath.isReadOnlyAccess()); + } + } + else + { + // Single file/folder search, convert the path to a store path + + try + { + storePath = buildStorePath(avmCtx, searchPath, sess); + } + catch ( AccessDeniedException ex) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Start search access denied"); + + throw new FileNotFoundException("Access denied"); + } + + // Get the single file/folder details + + AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath()); + + if (nodeDesc != null) + { + // Create the search context for the single file/folder + + context = new AVMSingleFileSearchContext(nodeDesc, storePath.getRelativePath(), storePath.isReadOnlyAccess()); + } + + } + + // Return the search context + + return context; + } + + /** + * Truncate a file to the specified size + * + * @param sess + * Server session + * @param tree + * Tree connection + * @param file + * Network file details + * @param siz + * New file length + * @exception java.io.IOException + * The exception description. + */ + public void truncateFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final long siz) + throws java.io.IOException + { + // Check if the file is a directory, or only has read access + + if (file.getGrantedAccess() <= NetworkFile.READONLY) + throw new AccessDeniedException(); + + // If the content channel is not open for the file then start a transaction + + AVMNetworkFile avmFile = (AVMNetworkFile) file; + + // Truncate or extend the file + if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false) + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + file.truncateFile(siz); + file.flushFile(); + return null; + }}); + } + else + { + file.truncateFile(siz); + file.flushFile(); + } + + + } + + /** + * 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, final NetworkFile file, final byte[] buf, final int bufoff, final int siz, + final long fileoff) throws java.io.IOException + { + // Check if the file is a directory, or only has read access + + if (file.isDirectory() || file.getGrantedAccess() <= NetworkFile.READONLY) + throw new AccessDeniedException(); + + // If the content channel is not open for the file, or the channel is not writable, then start a transaction + + AVMNetworkFile avmFile = (AVMNetworkFile) file; + + // Write the data to the file + if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false) + { + doInWriteTransaction(sess, new CallableIO(){ + + public Void call() throws IOException + { + file.writeFile(buf, siz, bufoff, fileoff); + return null; + }}); + } + else + { + file.writeFile(buf, siz, bufoff, fileoff); + } + + + // Return the actual write length + + return siz; + } + + /** + * 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 + } + + /** + * Find the pseudo file for a virtual path + * + * @param avmPath + * AVMPath + * @param avmCtx + * AVMContext + * @return PseudoFile + */ + private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx) + { + return findPseudoFolder(avmPath, avmCtx, true); + } + + /** + * Find the pseudo file for a virtual path + * + * @param avmPath + * AVMPath + * @param avmCtx + * AVMContext + * @param generateStates + * boolean + * @return PseudoFile + */ + private final PseudoFile findPseudoFolder(AVMPath avmPath, AVMContext avmCtx, boolean generateStates) + { + // Check if the path is to a store pseudo folder + + if (avmPath.isRootPath()) + return null; + + // Get the file state for the parent of the required folder + + FileState fstate = null; + StringBuilder str = null; + PseudoFile psFile = null; + + switch (avmPath.isLevel()) + { + // Store root folder + + case StoreRoot: + + // Get the root folder file state + + fstate = avmCtx.getStateCache().findFileState(FileName.DOS_SEPERATOR_STR); + + if (fstate != null && fstate.hasPseudoFiles()) + psFile = fstate.getPseudoFileList().findFile(avmPath.getStoreName(), false); + break; + + // Versions root or Head folder + + case VersionRoot: + case Head: + + // Create a path to the parent store + + str = new StringBuilder(); + + str.append(FileName.DOS_SEPERATOR); + str.append(avmPath.getStoreName()); + + // Find/create the file state for the store + + AVMPath storePath = new AVMPath(str.toString()); + fstate = findPseudoState(storePath, avmCtx); + + // Find the version root or head pseudo folder + + if (fstate != null) + { + if (avmPath.isLevel() == AVMPath.LevelId.Head) + psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionNameHead, true); + else + psFile = fstate.getPseudoFileList().findFile(AVMPath.VersionsFolder, true); + } + break; + + // Version folder + + case Version: + + // Create a path to the versions folder + + str = new StringBuilder(); + + str.append(FileName.DOS_SEPERATOR); + str.append(avmPath.getStoreName()); + str.append(FileName.DOS_SEPERATOR); + str.append(AVMPath.VersionsFolder); + + // Find/create the file state for the store + + AVMPath verrootPath = new AVMPath(str.toString()); + fstate = findPseudoState(verrootPath, avmCtx); + + // Find the version pseudo file + + if (fstate != null) + { + // Build the version folder name string + + str.setLength(0); + + str.append(AVMPath.VersionFolderPrefix); + str.append(avmPath.getVersion()); + + // find the version folder pseduo file + + psFile = fstate.getPseudoFileList().findFile(str.toString(), true); + } + break; + + // Head data or metadata folder + + case HeadData: + case HeadMetaData: + + // Create a path to the head folder + + str = new StringBuilder(); + + str.append(FileName.DOS_SEPERATOR); + str.append(avmPath.getStoreName()); + str.append(FileName.DOS_SEPERATOR); + str.append(AVMPath.VersionNameHead); + + // Find/create the file state for the store + + AVMPath headPath = new AVMPath(str.toString()); + fstate = findPseudoState(headPath, avmCtx); + + // Find the data or metadata pseudo folder + + if (fstate != null) + { + // Find the pseudo folder + + if (avmPath.isLevel() == AVMPath.LevelId.HeadData) + { + psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true); + } + else + { + psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true); + } + } + break; + + // Version data or metadata folder + + case VersionData: + case VersionMetaData: + + // Create a path to the version folder + + str = new StringBuilder(); + + str.append(FileName.DOS_SEPERATOR); + str.append(avmPath.getStoreName()); + str.append(FileName.DOS_SEPERATOR); + str.append(AVMPath.VersionFolderPrefix); + str.append(avmPath.getVersion()); + + // Find/create the file state for the store + + AVMPath verPath = new AVMPath(str.toString()); + fstate = findPseudoState(verPath, avmCtx); + + // Find the data or metadata pseudo folder + + if (fstate != null) + { + // Find the pseudo folder + + if (avmPath.isLevel() == AVMPath.LevelId.VersionData) + { + psFile = fstate.getPseudoFileList().findFile(AVMPath.DataFolder, true); + } + else + { + psFile = fstate.getPseudoFileList().findFile(AVMPath.MetaDataFolder, true); + } + } + break; + } + + // Check if the pseudo file was not found but file states should be generated + + if (psFile == null && generateStates == true) + { + // Generate the file states for the path, this is required if a request is made to a path without + // walking the folder tree + + generatePseudoFolders(avmPath, avmCtx); + + // Try and find the pseudo file again + + psFile = findPseudoFolder(avmPath, avmCtx, false); + } + + // Return the pseudo file, or null if not found + + return psFile; + } + + /** + * Find the file state for a pseudo folder path + * + * @param avmPath + * AVMPath + * @param avmCtx + * AVMContext + * @return FileState + */ + protected final FileState findPseudoState(AVMPath avmPath, AVMContext avmCtx) + { + // Make sure the is to a pseudo file/folder + + if ( avmPath.isPseudoPath() == false) + return null; + + // Check if there are any new stores to be added to the virtualization view + + if ( avmCtx.hasNewStoresQueued()) { + + // Get the new stores list, there is a chance another thread might get the queue, if the queue is empty + // another thread is processing it + + StringList storeNames = avmCtx.getNewStoresQueue(); + + while ( storeNames.numberOfStrings() > 0) { + + // Get the current store name + + String curStoreName = storeNames.removeStringAt( 0); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Adding new store " + curStoreName); + + // Add the current store to the virtualization view + + addNewStore( avmCtx, curStoreName); + } + + // Get the root folder file state, update the modification timestamp + + FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); + if ( rootState != null) + rootState.updateModifyDateTime(); + } + + // Check if the path is to a store pseudo folder + + FileState fstate = null; + StringBuilder str = null; + String relPath = null; + + switch ( avmPath.isLevel()) + { + // Root of the hieararchy + + case Root: + + // Get the root path file state + + fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); + + // Check if the root file state is valid + + if ( fstate == null) + { + // Create a file state for the root folder + + fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true); + fstate.setExpiryTime( FileState.NoTimeout); + fstate.setFileStatus( DirectoryExists); + + // Set the modification timestamp for the root folder + + fstate.updateModifyDateTime(); + + // Get a list of the available AVM stores + + List storeList = m_avmService.getStores(); + + if ( storeList != null && storeList.size() > 0) + { + // Add pseudo files for the stores + + for ( AVMStoreDescriptor storeDesc : storeList) + { + // Get the properties for the current store + + String storeName = storeDesc.getName(); + Map props = m_avmService.getStoreProperties( storeName); + + // Check if the store is a main web project + + if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN)) + { + // Get the noderef for the web project + + PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF); + if ( prop != null) { + + // Get the web project noderef + + NodeRef webNodeRef = new NodeRef( prop.getStringValue()); + + if (m_nodeService.exists(webNodeRef)) + { + // Create the web project pseudo folder + + WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef); + fstate.addPseudoFile( webProjFolder); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "Found web project " + webProjFolder.getFileName()); + + // Get the list of content managers for this web project + + List mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + + for ( ChildAssociationRef mgrRef : mgrAssocs) + { + // Get the child node and see if it is a content manager association + + NodeRef childRef = mgrRef.getChildRef(); + + if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER)) + { + // Get the user name add it to the web project pseudo folder + + String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME); + + webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug(" Added content manager " + userName); + } + } + } + else + { + logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef); + } + } + } + else + { + // Check if this store is a web project sandbox + + int storeType = StoreType.Normal; + String webProjName = null; + String userName = null; + + if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) + { + // Sandbox store, linked to a web project + + storeType = StoreType.WebAuthorMain; + + // Get the associated web project name + + webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue(); + + // Get the user name from the store name + + userName = storeName.substring( webProjName.length() + 2); + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW)) + { + // Author preview sandbox store, linked to a web project + + storeType = StoreType.WebAuthorPreview; + + // Get the associated web project name + + String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length()); + int pos = projPlusUser.lastIndexOf("--"); + if ( pos != -1) + { + webProjName = projPlusUser.substring( 0, pos); + userName = projPlusUser.substring(pos + 2); + } + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW)) + { + // Staging preview sandbox store, linked to a web project + + storeType = StoreType.WebStagingPreview; + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW)) + { + // Staging preview sandbox store, linked to a web project + + storeType = StoreType.WebStagingPreview; + + // Get the associated web project name + + webProjName = storeName.substring( 0, storeName.length() - "--preview".length()); + } + else if ( props.containsKey(QName.createQName(null, ".sitestore"))) + { + // Site data store type + + storeType = StoreType.SiteStore; + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName); + + // Add a pseudo file for the current store + + if ( avmCtx.showStoreType( storeType)) + { + // Create the pseudo folder for the store + + StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType); + if (storeType == StoreType.WebAuthorMain || storeType == StoreType.WebAuthorPreview || + storeType == StoreType.WebStagingMain || storeType == StoreType.WebStagingPreview) + { + storeFolder.setWebProject( webProjName); + storeFolder.setUserName( userName); + } + + // Add the store pseudo folder to the root folder file list + + fstate.addPseudoFile( storeFolder); + } + } + } + } + + // Scan the pseudo folder list and add all publisher/reviewer user names to the web project roles list + + PseudoFileList folderList = fstate.getPseudoFileList(); + if ( folderList != null && folderList.numberOfFiles() > 0) + { + // Scan the pseudo folder list + + for ( int i = 0; i < folderList.numberOfFiles(); i++) + { + // Check if the current pseduo file is a store folder + + if ( folderList.getFileAt( i) instanceof StorePseudoFile) + { + // Check if the store has an associated web project + + StorePseudoFile curFile = (StorePseudoFile) folderList.getFileAt( i); + if ( curFile.hasWebProject()) + { + // Find the associated web project pseudo folder + + WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( curFile.getWebProject(), true); + + if (webProj == null) + { + logger.warn("Missing web project for: "+curFile.getFileName()+" ("+curFile.getWebProject()+")"); + } + else + { + // Strip the web project name from the sandbox store name and extract the user name. + // Add the user as a publisher/reviewer to the web project roles list + + String userName = curFile.getFileName().substring( webProj.getFileName().length() + 2); + + // If the user does not have a content manager role then add as a publisher + + if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone) + { + webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( "Added publisher " + userName + " to " + webProj.getFileName()); + } + } + } + } + } + } + } + break; + + // Store folder + + case StoreRoot: + + // Build the path to the parent store folder + + str = new StringBuilder(); + + str.append( FileName.DOS_SEPERATOR); + str.append( avmPath.getStoreName()); + + // Search for the file state for the store pseudo folder + + relPath = str.toString(); + fstate = avmCtx.getStateCache().findFileState( relPath); + + if ( fstate == null) + { + // Create a file state for the store path + + fstate = avmCtx.getStateCache().findFileState( str.toString(), true); + fstate.setFileStatus( DirectoryExists); + + // Add a pseudo file for the head version + + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.VersionNameHead); + + fstate.addPseudoFile( new VersionPseudoFile( AVMPath.VersionNameHead, str.toString())); + + // Add a pseudo file for the version root folder + + str.setLength( relPath.length() + 1); + str.append( AVMPath.VersionsFolder); + + fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.VersionsFolder, str.toString())); + } + break; + + // Head folder + + case Head: + + // Build the path to the store head version folder + + str = new StringBuilder(); + + str.append( FileName.DOS_SEPERATOR); + str.append( avmPath.getStoreName()); + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.VersionNameHead); + + // Search for the file state for the store head version pseudo folder + + relPath = str.toString(); + + fstate = avmCtx.getStateCache().findFileState( relPath); + + if ( fstate == null) + { + // Create a file state for the store head folder path + + fstate = avmCtx.getStateCache().findFileState( str.toString(), true); + fstate.setFileStatus( DirectoryExists); + + // Add a pseudo file for the data pseudo folder + + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.DataFolder); + + fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); + + // Add a pseudo file for the metadata pseudo folder + + str.setLength( relPath.length() + 1); + str.append( AVMPath.MetaDataFolder); + + fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); + } + break; + + // Version root folder + + case VersionRoot: + + // Get the list of AVM store versions + + try + { + // Build the path to the parent store folder + + str = new StringBuilder(); + + str.append( FileName.DOS_SEPERATOR); + str.append( avmPath.getStoreName()); + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.VersionsFolder); + + // Create a file state for the store path + + relPath = str.toString(); + fstate = avmCtx.getStateCache().findFileState( relPath, true); + fstate.setFileStatus( DirectoryExists); + + // Add pseudo folders if the list is empty + + if ( fstate.hasPseudoFiles() == false) + { + // Build the version folder name for the head version + + StringBuilder verStr = new StringBuilder( AVMPath.VersionFolderPrefix); + verStr.append( "-1"); + + // Add a pseudo file for the head version + + str.append( FileName.DOS_SEPERATOR); + str.append( verStr.toString()); + + fstate.addPseudoFile( new VersionPseudoFile( verStr.toString(), str.toString())); + + // Get the list of versions for the store + + List verList = m_avmService.getStoreVersions( avmPath.getStoreName()); + + // Add pseudo files for the versions to the store state + + if ( verList.size() > 0) + { + for ( VersionDescriptor verDesc : verList) + { + // Generate the version string + + String verName = null; + + verStr.setLength( AVMPath.VersionFolderPrefix.length()); + verStr.append( verDesc.getVersionID()); + + verName = verStr.toString(); + + str.setLength( relPath.length() + 1); + str.append( verName); + + // Add the version pseudo folder + + fstate.addPseudoFile( new VersionPseudoFile ( verName, verDesc, str.toString())); + } + } + } + } + catch ( AVMNotFoundException ex) + { + // Invalid store name + } + break; + + // Version folder + + case Version: + + // Build the path to the store version folder + + str = new StringBuilder(); + + str.append( FileName.DOS_SEPERATOR); + str.append( avmPath.getStoreName()); + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.VersionFolderPrefix); + str.append( avmPath.getVersion()); + + // Search for the file state for the version pseudo folder + + relPath = str.toString(); + fstate = avmCtx.getStateCache().findFileState( relPath); + + if ( fstate == null) + { + // Create a file state for the version folder path + + fstate = avmCtx.getStateCache().findFileState( str.toString(), true); + fstate.setFileStatus( DirectoryExists); + + // Add a pseudo file for the data pseudo folder + + str.append( FileName.DOS_SEPERATOR); + str.append( AVMPath.DataFolder); + + fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); + + // Add a pseudo file for the metadata pseudo folder + + str.setLength( relPath.length() + 1); + str.append( AVMPath.MetaDataFolder); + + fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); + } + break; + } + + // Return the file state + + return fstate; + } + + /** + * Generate the pseudo folders for the specified path + * + * @param avmPath + * AVMPath + * @param avmCtx + * AVMContext + */ + private final void generatePseudoFolders(AVMPath avmPath, AVMContext avmCtx) + { + // Create the root file state + + AVMPath createPath = new AVMPath(); + StringBuilder pathStr = new StringBuilder(); + + pathStr.append(FileName.DOS_SEPERATOR); + createPath.parsePath(pathStr.toString()); + + FileState rootState = findPseudoState(createPath, avmCtx); + + // Check if the path has a store name + + if (avmPath.getStoreName() != null) + { + // Check if the store name is valid + + if (rootState.hasPseudoFiles() + && rootState.getPseudoFileList().findFile(avmPath.getStoreName(), false) != null) + { + // Create the store file state + + pathStr.append(avmPath.getStoreName()); + pathStr.append(FileName.DOS_SEPERATOR); + + createPath.parsePath(pathStr.toString()); + + findPseudoState(createPath, avmCtx); + + // Add the head and version root pseudo folders + + createPath.parsePath(pathStr.toString() + AVMPath.VersionNameHead); + findPseudoState(createPath, avmCtx); + + createPath.parsePath(pathStr.toString() + AVMPath.VersionsFolder); + findPseudoState(createPath, avmCtx); + + // Check if the path is to a version folder + + if (avmPath.isLevel().ordinal() >= AVMPath.LevelId.Version.ordinal()) + { + // Build the path + + pathStr.append(AVMPath.VersionsFolder); + pathStr.append(FileName.DOS_SEPERATOR); + pathStr.append(AVMPath.VersionFolderPrefix); + pathStr.append(avmPath.getVersion()); + + createPath.parsePath(pathStr.toString()); + + // Generate the version folders + + findPseudoState(createPath, avmCtx); + } + } + } + } + + /** + * Check that the user has access to the path + * + * @param avmPath AVMPath + * @param avmCtx AVMContext + * @param sess SrvSession + * @exception AccessDeniedException + */ + private final void checkPathAccess( AVMPath avmPath, AVMContext avmCtx, SrvSession sess) + throws AccessDeniedException { + + // Only enforce access checks on virtualization views + + if ( avmCtx.isVirtualizationView() == false) + return; + + // Get the client details for the session + + ClientInfo cInfo = sess.getClientInformation(); + if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0) + throw new AccessDeniedException(); + + // Allow access to the root folder + + if ( avmPath.isLevel() == AVMPath.LevelId.Root || avmPath.isLevel() == AVMPath.LevelId.HeadData || avmPath.isLevel() == AVMPath.LevelId.StoreRootPath ) { + + // Allow read only access to the root, www and avm_webapps folders + avmPath.setReadOnlyAccess(true); + return; + } + + // Get root file state, get the store pseudo folder details + + FileState rootState = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR); + if ( rootState == null){ + + // Recreate the root file state, new stores may have been added + + rootState = findPseudoState( new AVMPath( FileName.DOS_SEPERATOR_STR), avmCtx); + } + + // Check if there are any store pseudo folders + + if ( rootState != null && rootState.hasPseudoFiles()) + { + PseudoFile pseudoFolder = rootState.getPseudoFileList().findFile( avmPath.getStoreName(), false); + if ( pseudoFolder != null) + { + // Check if the pseudo folder is a web project folder or sandbox within a web project + + String curUserName = m_authComponent.getCurrentUserName(); + + if ( pseudoFolder instanceof WebProjectStorePseudoFile) + { + // Check the users role within the web project + + WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder; + + int role = webFolder.getUserRole( curUserName); + + if ( role == WebProjectStorePseudoFile.RoleNone) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("User " + curUserName + " has no access to web project, " + webFolder.getFileName()); + + // User does not have access to this web project + + throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName()); + } + else if ( avmCtx.allowAdminStagingWrites() && cInfo.isAdministrator()) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("User " + curUserName + " granted write access to web project, " + webFolder.getFileName()); + + // Allow admin write access + + avmPath.setReadOnlyAccess( false); + } + else + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("User " + curUserName + " granted read-only access to web project, " + webFolder.getFileName()); + + // Only allow read-only access to the staging area + + avmPath.setReadOnlyAccess( true); + } + } + else if ( pseudoFolder instanceof StorePseudoFile) + { + // Check the store type + + StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder; + if ( storeFolder.isStoreType() == StoreType.Normal) + return; + else if ( storeFolder.hasWebProject()) + { + // Get the web project that the sandbox is linked to + + WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) rootState.getPseudoFileList().findFile( storeFolder.getWebProject(), false); + + int role = webFolder.getUserRole( curUserName); + + if ( role == WebProjectStorePseudoFile.RoleNone) + { + // User does not have access to this web project + + throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName()); + } + else if ( role == WebProjectStorePseudoFile.RolePublisher && + storeFolder.getUserName().equalsIgnoreCase( curUserName) == false) + { + // User does not have access to this web project + + throw new AccessDeniedException("User " + curUserName + " has no access to web project, " + webFolder.getFileName() + "/" + storeFolder.getFileName()); + } + } + } + } + } + else + { + // Store does not exist + + throw new AccessDeniedException("Store does not exist, " + avmPath.getStoreName()); + } + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug( "Check access " + avmPath); + } + + /** + * Filter the list of pseudo folders returned in a search + * + * @param avmCtx AVMContext + * @param sess SrvSession + * @param avmPath AVMPath + * @param fstate FileState + * @return PseudoFileList + */ + private final PseudoFileList filterPseudoFolders( AVMContext avmCtx, SrvSession sess, AVMPath avmPath, FileState fstate) + { + // Check if the root folder file state has any store pseudo folders + + if ( fstate.hasPseudoFiles() == false) + return null; + + // Get the client details for the session + + ClientInfo cInfo = sess.getClientInformation(); + if ( cInfo == null || cInfo.getUserName() == null || cInfo.getUserName().length() == 0) + return null; + + // Check for the admin user, no need to filter the list + + PseudoFileList fullList = fstate.getPseudoFileList(); + if ( cInfo.isAdministrator()) + return fullList; + + // Create a filtered list of store pseudo folders that the user has access to + + PseudoFileList filterList = new PseudoFileList(); + String userName = m_authComponent.getCurrentUserName(); + + for ( int i = 0; i < fullList.numberOfFiles(); i++) + { + // Get the current store pseudo folder + + PseudoFile pseudoFolder = fullList.getFileAt( i); + + // Check if the pseudo folder is a web project folder or sandbox within a web project + + if ( pseudoFolder instanceof WebProjectStorePseudoFile) + { + // Check the users role within the web project + + WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) pseudoFolder; + + if ( avmCtx.showStagingStores() && webFolder.getUserRole( userName) != WebProjectStorePseudoFile.RoleNone) + { + // User has access to this store + + filterList.addFile( pseudoFolder); + } + } + else if ( pseudoFolder instanceof StorePseudoFile) + { + // Check if the store type should be included in the visible list + + StorePseudoFile storeFolder = (StorePseudoFile) pseudoFolder; + if ( avmCtx.showStoreType( storeFolder.isStoreType())) + { + // Check if the user has access to this store + + if ( storeFolder.hasWebProject()) + { + // Get the web project that the sandbox is linked to + + WebProjectStorePseudoFile webFolder = (WebProjectStorePseudoFile) fullList.findFile( storeFolder.getWebProject(), false); + + if ( webFolder != null) { + int role = webFolder.getUserRole( userName); + + if ( role == WebProjectStorePseudoFile.RoleContentManager && avmCtx.showStoreType( storeFolder.isStoreType())) + { + // User is a content manager, allow access to the store + + filterList.addFile( storeFolder); + } + else if ( role == WebProjectStorePseudoFile.RolePublisher && avmCtx.showStoreType( storeFolder.isStoreType())) + { + // Allow access if the user owns the current folder + + if ( storeFolder.getUserName().equalsIgnoreCase( userName)) + filterList.addFile( storeFolder); + } + } + else if ( logger.isDebugEnabled()) + logger.debug("Cannot find associated web folder for store " + storeFolder.getFileName()); + + } + else if ( avmCtx.showNormalStores() || avmCtx.showSiteStores()) + { + // Store is not linked to a web project, allow access to the store + + filterList.addFile( storeFolder); + } + } + } + } + + // Return the filtered list + + return filterList; + } + + /** + * Add a new store to the top level folder list + * + * @param avmCtx AVMContext + * @param storeName String + */ + protected void addNewStore( AVMContext avmCtx, String storeName) { + + // Get the root folder file state + + FileState fstate = avmCtx.getStateCache().findFileState( FileName.DOS_SEPERATOR_STR, true); + if ( fstate == null) + return; + + // Get the properties for the store + + AVMStoreDescriptor storeDesc = m_avmService.getStore( storeName); + if ( storeDesc == null) + return; + + Map props = m_avmService.getStoreProperties( storeName); + + // Check if the store is a main web project + + if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_MAIN)) + { + // Get the noderef for the web project + + PropertyValue prop = props.get( SandboxConstants.PROP_WEB_PROJECT_NODE_REF); + if ( prop != null) { + + // Get the web project noderef + + NodeRef webNodeRef = new NodeRef( prop.getStringValue()); + + if (m_nodeService.exists(webNodeRef)) + { + // Create the web project pseudo folder + + WebProjectStorePseudoFile webProjFolder = new WebProjectStorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, webNodeRef); + fstate.addPseudoFile( webProjFolder); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( " Found web project " + webProjFolder.getFileName()); + + // Get the list of content managers for this web project + + List mgrAssocs = m_nodeService.getChildAssocs( webNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + + for ( ChildAssociationRef mgrRef : mgrAssocs) + { + // Get the child node and see if it is a content manager association + + NodeRef childRef = mgrRef.getChildRef(); + + if ( m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERROLE).equals(ROLE_CONTENT_MANAGER)) + { + // Get the user name add it to the web project pseudo folder + + String userName = (String) m_nodeService.getProperty( childRef, WCMAppModel.PROP_WEBUSERNAME); + + webProjFolder.addUserRole( userName, WebProjectStorePseudoFile.RoleContentManager); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug(" Added content manager " + userName); + } + } + } + else + { + logger.warn("AVM Store '"+storeName+"' with webProjectNodeRef that does not exist: "+webNodeRef); + } + } + } + else + { + // Check if this store is a web project sandbox + + int storeType = StoreType.Normal; + String webProjName = null; + String userName = null; + + if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) + { + // Sandbox store, linked to a web project + + storeType = StoreType.WebAuthorMain; + + // Get the associated web project name + + webProjName = props.get( SandboxConstants.PROP_WEBSITE_NAME).getStringValue(); + + // Get the user name from teh store name + + userName = storeName.substring( webProjName.length() + 2); + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW)) + { + // Author preview sandbox store, linked to a web project + + storeType = StoreType.WebAuthorPreview; + + // Get the associated web project name + + String projPlusUser = storeName.substring( 0, storeName.length() - "--preview".length()); + int pos = projPlusUser.lastIndexOf("--"); + if ( pos != -1) + { + webProjName = projPlusUser.substring( 0, pos); + userName = projPlusUser.substring(pos + 2); + } + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW)) + { + // Staging preview sandbox store, linked to a web project + + storeType = StoreType.WebStagingPreview; + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW)) + { + // Staging preview sandbox store, linked to a web project + + storeType = StoreType.WebStagingPreview; + + // Get the associated web project name + + webProjName = storeName.substring( 0, storeName.length() - "--preview".length()); + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( " Store " + storeDesc.getName() + ", type=" + StoreType.asString( storeType) + ", webproj=" + webProjName + ", username=" + userName); + + // Add a pseudo file for the current store + + if ( avmCtx.showStoreType( storeType)) + { + // Create the pseudo folder for the store + + StorePseudoFile storeFolder = new StorePseudoFile( storeDesc, FileName.DOS_SEPERATOR_STR + storeName, storeType); + if ( storeType != StoreType.Normal) + { + storeFolder.setWebProject( webProjName); + storeFolder.setUserName( userName); + + // Add all publisher/reviewer user names to the web project roles list + + if ( storeFolder.hasWebProject()) + { + // Find the associated web project pseudo folder + + PseudoFileList folderList = fstate.getPseudoFileList(); + if ( folderList != null) { + + // Find the associated web project + + WebProjectStorePseudoFile webProj = (WebProjectStorePseudoFile) folderList.findFile( storeFolder.getWebProject(), true); + + if ( webProj != null) { + + // Strip the web project name from the sandbox store name and extract the user name. + // Add the user as a publisher/reviewer to the web project roles list + + userName = storeFolder.getFileName().substring( webProj.getFileName().length() + 2); + + // If the user does not have a content manager role then add as a publisher + + if ( webProj.getUserRole( userName) == WebProjectStorePseudoFile.RoleNone) + { + webProj.addUserRole( userName, WebProjectStorePseudoFile.RolePublisher); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug( " Added publisher " + userName + " to " + webProj.getFileName()); + } + } + } + } + } + + // Add the store pseudo folder to the root folder file list + + fstate.addPseudoFile( storeFolder); + } + } + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 133c01a9b7..88db525e03 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -554,14 +554,14 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi String dstParent = dst.getSecond(); String dstName = assocQName.getLocalName(); // TODO Invoke policy behavior. Not quite sure how to translate this. -// NodeRef oldParentRef = AVMNodeConverter.ToNodeRef(-1, srcParent); -// ChildAssociationRef oldAssocRef = -// new ChildAssociationRef(assocTypeQName, -// oldParentRef, -// QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, srcName), -// nodeToMoveRef, -// true, -// -1); + NodeRef oldParentRef = AVMNodeConverter.ToNodeRef(-1, srcParent); + ChildAssociationRef oldAssocRef = + new ChildAssociationRef(assocTypeQName, + oldParentRef, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, srcName), + nodeToMoveRef, + true, + -1); // invokeBeforeDeleteChildAssociation(oldAssocRef); String dstPath = AVMNodeConverter.ExtendAVMPath(dstParent, dstName); NodeRef newChildRef = AVMNodeConverter.ToNodeRef(-1, dstPath); @@ -579,6 +579,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi newChildRef, true, -1); + invokeOnMoveNode(oldAssocRef, newAssocRef); // invokeOnCreateChildAssociation(newAssocRef); // invokeOnDeleteChildAssociation(oldAssocRef); // invokeOnUpdateNode(oldParentRef); diff --git a/source/java/org/alfresco/repo/avm/LayeredFolderType.java b/source/java/org/alfresco/repo/avm/LayeredFolderType.java new file mode 100644 index 0000000000..e3d2d48871 --- /dev/null +++ b/source/java/org/alfresco/repo/avm/LayeredFolderType.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . */ + +package org.alfresco.repo.avm; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.WCMAppModel; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.util.AVMUtil; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Class defines policy behaviour for avmlayeredfolder type + * + * @author Ivan Rybnikov + * @since 4.0.2 + */ +public class LayeredFolderType implements NodeServicePolicies.OnMoveNodePolicy +{ + + private static Log logger = LogFactory.getLog(LayeredFolderType.class); + + /** The policy component */ + private PolicyComponent policyComponent; + + private FileFolderService fileFolderService; + + transient private AVMService avmService; + + /** Used to determine was the policy executed by Web-Client operation */ + private static ThreadLocal issuedByWebClient = new ThreadLocal(); + + /** Default max time allowed for the external issuers (CIFS, FTP) */ + private static long EXTERNAL_MAX_TIME = 2000; + + /** Max time allowed for the external issuers (CIFS, FTP). + * can be overridden by bean definition */ + private long maxTime = EXTERNAL_MAX_TIME; + + /** + * Initialize the avmlayeredfolder type policies + */ + + public void init() + { + if (maxTime < 0) + { + logger.warn( + "Unable to set 'wcm.rename.max.time.milliseconds' property value: '" + maxTime + " ms', " + + "set default value: '" + EXTERNAL_MAX_TIME + " ms'"); + setMaxTime(EXTERNAL_MAX_TIME); + } + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + PropertyCheck.mandatory(this, "avmService", avmService); + + this.policyComponent.bindClassBehaviour( + NodeServicePolicies.OnMoveNodePolicy.QNAME, + WCMModel.TYPE_AVM_LAYERED_FOLDER, + new JavaBehaviour(this, "onMoveNode", Behaviour.NotificationFrequency.EVERY_EVENT)); + } + + @Override + public synchronized void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + // Storing start time + long startTime = System.currentTimeMillis(); + + NodeRef oldNodeRef = oldChildAssocRef.getChildRef(); + NodeRef newNodeRef = newChildAssocRef.getChildRef(); + String name = newChildAssocRef.getQName().getLocalName(); + + String[] splittedPath = AVMUtil.splitPath(AVMNodeConverter.ToAVMVersionPath(oldNodeRef).getSecond()); + // Get AVM store name + String avmStore = splittedPath[0]; + // Get stale path + String oldPathToReplace = splittedPath[1]; + // Create new path + String newPathToInsert = oldPathToReplace.substring(0, oldPathToReplace.lastIndexOf(AVMUtil.AVM_PATH_SEPARATOR_CHAR) + 1) + name; + + AVMNodeDescriptor desc = avmService.lookup(-1, AVMNodeConverter.ToAVMVersionPath(newNodeRef).getSecond()); + + if (desc.isDirectory()) + { + // Create the initial list of descriptors + List descriptors = new ArrayList(10); + descriptors.add(desc); + + // List of next level of descriptors + List nextDescriptors = new ArrayList(10); + + while (descriptors.size() != 0) + { + if (nextDescriptors != null) + { + nextDescriptors.clear(); + } + + for (AVMNodeDescriptor curDescriptor : descriptors) + { + Map listing = avmService.getDirectoryListing(curDescriptor); + if (listing.size() == 0) + { + continue; + } + + for (AVMNodeDescriptor descriptor : listing.values()) + { + if (descriptor.isDirectory()) + { + nextDescriptors.add(descriptor); + } + else if (descriptor.isFile()) + { + refreshPath(descriptor, avmStore, oldPathToReplace, newPathToInsert, startTime); + } + } + } + + descriptors.clear(); + if(nextDescriptors != null) + { + descriptors.addAll(nextDescriptors); + } + } + } + } + + private void refreshPath(AVMNodeDescriptor descriptor, String avmStore, String oldPathToReplace, String newPathToInsert, long startTime) + { + checkDuration(startTime); + String path = descriptor.getPath(); + + // If file is a form instance data refresh its paths to renditions + if (avmService.hasAspect(-1, path, WCMAppModel.ASPECT_FORM_INSTANCE_DATA)) + { + refreshFormInstanceData(avmStore, oldPathToReplace, newPathToInsert, -1, path); + } + // If file is a rendition find its form instance data and refresh its paths to renditions + else if (avmService.hasAspect(-1, path, WCMAppModel.ASPECT_RENDITION)) + { + refreshRendition(avmStore, oldPathToReplace, newPathToInsert, -1, path); + } + } + + private void refreshFormInstanceData(String avmStore, String pathToReplace, String pathToInsert, int nodeVersion, String nodePath) + { + // Get all renditions + final PropertyValue pv = avmService.getNodeProperty(nodeVersion, nodePath, WCMAppModel.PROP_RENDITIONS); + + // Skip if there is no renditions. + if (pv == null) + return; + + final Collection renditionPaths = pv.getCollection(DataTypeDefinition.TEXT); + final List newRenditionPaths = new ArrayList(renditionPaths.size()); + + // Refresh all renditions paths + for (Serializable renditionPath : renditionPaths) + { + // Get old path which become stale + String oldPath = (String) renditionPath; + + // Refresh path to make it actual + String newPath = oldPath.replaceFirst(pathToReplace, pathToInsert); + newRenditionPaths.add(newPath); + + // Refresh property WCMAppModel.PROP_PRIMARY_FORM_INSTANCE_DATA of the rendition + // which is the path from rendition to the form instance data + String fullRenditionPath = avmStore + ":" + newPath; + String primaryFormInstanceData = avmService.getNodeProperty(-1, fullRenditionPath, WCMAppModel.PROP_PRIMARY_FORM_INSTANCE_DATA).getStringValue(); + primaryFormInstanceData = primaryFormInstanceData.replaceFirst(pathToReplace, pathToInsert); + avmService.setNodeProperty(fullRenditionPath, WCMAppModel.PROP_PRIMARY_FORM_INSTANCE_DATA, new PropertyValue(DataTypeDefinition.TEXT, + primaryFormInstanceData)); + } + avmService.setNodeProperty(nodePath, WCMAppModel.PROP_RENDITIONS, new PropertyValue(DataTypeDefinition.TEXT, (Serializable) newRenditionPaths)); + + } + + private void refreshRendition(String avmStore, String pathToReplace, String pathToInsert, int nodeVersion, String nodePath) + { + // Get form instance data + String primaryFormInstanceData = avmService.getNodeProperty(nodeVersion, nodePath, WCMAppModel.PROP_PRIMARY_FORM_INSTANCE_DATA).getStringValue(); + + // If form instance data is under the same folder skip it because it will be processed by refreshFormInstanceData() method + if (primaryFormInstanceData.startsWith(pathToReplace + AVMUtil.AVM_PATH_SEPARATOR_CHAR)) + return; + + // If form instance data contains new path skip it because it have already been processed + if (primaryFormInstanceData.startsWith(pathToInsert + AVMUtil.AVM_PATH_SEPARATOR_CHAR)) + return; + + // Get paths to renditions + final PropertyValue pv = avmService.getNodeProperty(-1, avmStore + ":" + primaryFormInstanceData, WCMAppModel.PROP_RENDITIONS); + + // Return if renditions list is empty + if (pv == null) + return; + + final Collection renditionPaths = pv.getCollection(DataTypeDefinition.TEXT); + final List newRenditionPaths = new ArrayList(renditionPaths.size()); + + // Refresh all paths + for (Serializable renditionPath : renditionPaths) + { + // Form instance data have already been refreshed + if (((String) renditionPath).startsWith(pathToInsert)) + return; + + String newPath = ((String) renditionPath).replaceFirst(pathToReplace, pathToInsert); + newRenditionPaths.add(newPath); + } + + avmService.setNodeProperty( + avmStore + ":" + primaryFormInstanceData, + WCMAppModel.PROP_RENDITIONS, + new PropertyValue(DataTypeDefinition.TEXT, (Serializable) newRenditionPaths)); + } + + /** + * Set the policy component + * + * @param policyComponent the policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Set the fileFolderService + * + * @param fileFolderService + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * Set the avmService + * + * @param avmService + */ + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Checks policy duration. + * If policy was fired by the external calls (FTP, CIFS) + * and exceeds value set in wcm.rename.max.time.milliseconds exception is thrown. + * Default time is set to 2 seconds. + * + * @throws AlfrescoRuntimeException if operation exceeds max time duration + */ + private void checkDuration(long startTime) + { + if (!isIssuedByWebClient() && ((System.currentTimeMillis() - startTime) > maxTime)) + { + logger.warn("Operation exceeds max time duration and was aborted"); + throw new AlfrescoRuntimeException("Operation exceeds max time duration and was aborted"); + } + } + + /** + * Thread variable used to determine whether policy + * fired by Web-Client or by external issuers (FTP, CIFS) + * + * @param isWebClient + */ + public static void setIssuedByWebClient(Boolean isWebClient) + { + issuedByWebClient.set(isWebClient); + } + + /** + * Get the issuedByWebClient + * + * @return issuedByWebClient + */ + public static Boolean isIssuedByWebClient() + { + Boolean result = issuedByWebClient.get(); + return result == null ? Boolean.FALSE : result; + } + + /** + * Set the max time + * + * @param maxTime + */ + public void setMaxTime(long maxTime) + { + this.maxTime = maxTime; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java b/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java index 2a591554e5..37197e7eb8 100644 --- a/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java +++ b/source/java/org/alfresco/repo/content/TenantRoutingFileContentStore.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -89,7 +89,13 @@ public class TenantRoutingFileContentStore extends AbstractRoutingContentStore i { allEnabledStores.add(tenantFileStores.get(tenantDomain)); // note: cache should only contain enabled stores } - return allEnabledStores; + + if (allEnabledStores.size() > 0) + { + return allEnabledStores; + } + + // drop through to ensure default content store has been init'ed } } return Arrays.asList(getTenantFileStore(tenantService.getCurrentUserDomain())); diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java index 81e85342f0..c41be2eb73 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -22,7 +22,7 @@ import java.security.Principal; import java.util.Date; import java.util.Properties; -import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; @@ -46,7 +46,6 @@ public class DescriptorStartupLog extends AbstractLifecycleBean // Dependencies private DescriptorService descriptorService; - private TenantService tenantService; private TransactionService transactionService; private final String SYSTEM_INFO_STARTUP = "system.info.startup"; @@ -60,14 +59,6 @@ public class DescriptorStartupLog extends AbstractLifecycleBean this.descriptorService = descriptorService; } - /** - * @param tenantService Tenant Service - */ - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - /** * @param transactionService service to tell about read-write mode */ @@ -200,7 +191,7 @@ public class DescriptorStartupLog extends AbstractLifecycleBean Object[] params = new Object[] { serverEdition, currentMode != LicenseMode.TEAM ? "" : (" " + currentMode), // only append TEAM - !tenantService.isEnabled() ? "" : (" Multi-Tenant"), + (!AuthenticationUtil.isMtEnabled() ? "" : (" Multi-Tenant")), currentVersion, currentSchemaVersion, installedRepoVersion, installedSchemaVersion}; logger.info(I18NUtil.getMessage(SYSTEM_INFO_STARTUP, params)); } diff --git a/source/java/org/alfresco/repo/domain/DomainTestSuite.java b/source/java/org/alfresco/repo/domain/DomainTestSuite.java index 3c0ec62411..e52595e21a 100644 --- a/source/java/org/alfresco/repo/domain/DomainTestSuite.java +++ b/source/java/org/alfresco/repo/domain/DomainTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -34,6 +34,7 @@ import org.alfresco.repo.domain.propval.PropertyValueDAOTest; import org.alfresco.repo.domain.qname.QNameDAOTest; import org.alfresco.repo.domain.query.CannedQueryDAOTest; import org.alfresco.repo.domain.solr.SOLRDAOTest; +import org.alfresco.repo.domain.tenant.TenantAdminDAOTest; import org.alfresco.repo.domain.usage.UsageDAOTest; /** @@ -62,6 +63,7 @@ public class DomainTestSuite extends TestSuite suite.addTestSuite(UsageDAOTest.class); suite.addTestSuite(CannedQueryDAOTest.class); suite.addTestSuite(SOLRDAOTest.class); + suite.addTestSuite(TenantAdminDAOTest.class); return suite; } diff --git a/source/java/org/alfresco/repo/domain/tenant/AbstractTenantAdminDAOImpl.java b/source/java/org/alfresco/repo/domain/tenant/AbstractTenantAdminDAOImpl.java new file mode 100644 index 0000000000..4f4763f86c --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/AbstractTenantAdminDAOImpl.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant; + +import java.io.Serializable; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.lookup.EntityLookupCache; +import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO; +import org.alfresco.util.Pair; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.extensions.surf.util.ParameterCheck; + + +/** + * Abstract implementation for TenantAdmin DAO. + *

+ * This provides basic services such as caching, but defers to the underlying implementation + * for CRUD operations for: + * + * alf_tenant + * + * @author janv + * @since 4.0 (thor) + */ +public abstract class AbstractTenantAdminDAOImpl implements TenantAdminDAO +{ + private final TenantEntityCallbackDAO tenantEntityDaoCallback; + + /** + * Cache for the Tenant entity:
+ * KEY: TenantDomain (String)
+ * VALUE: TenantEntity
+ * VALUE KEY: None
+ */ + private EntityLookupCache tenantEntityCache; + + /** + * Set the cache to use for alf_tenant lookups (optional). + * + * @param tenantEntityCache the cache of tenantDomains to TenantEntities + */ + public void setTenantEntityCache(SimpleCache tenantEntityCache) + { + this.tenantEntityCache = new EntityLookupCache( + tenantEntityCache, + tenantEntityDaoCallback); + } + + /** + * Default constructor. + *

+ * This sets up the DAO accessor to bypass any caching to handle the case where the caches are not + * supplied in the setters. + */ + public AbstractTenantAdminDAOImpl() + { + this.tenantEntityDaoCallback = new TenantEntityCallbackDAO(); + this.tenantEntityCache = new EntityLookupCache(tenantEntityDaoCallback); + } + + public TenantEntity createTenant(TenantEntity entity) + { + ParameterCheck.mandatory("entity", entity); + ParameterCheck.mandatoryString("entity.tenantDomain", entity.getTenantDomain()); + + if (entity.getEnabled() == null) + { + entity.setEnabled(true); + } + + // force lower-case on create + entity.setTenantDomain(entity.getTenantDomain().toLowerCase()); + + entity.setVersion(0L); + + Pair entityPair = tenantEntityCache.getOrCreateByValue(entity); + return entityPair.getSecond(); + } + + public TenantEntity getTenant(String tenantDomain) + { + return getTenantImpl(tenantDomain); + } + + private TenantEntity getTenantImpl(String tenantDomain) + { + Pair entityPair = tenantEntityCache.getByKey(tenantDomain); + if (entityPair == null) + { + // try lower-case to make sure + entityPair = tenantEntityCache.getByKey(tenantDomain.toLowerCase()); + if (entityPair == null) + { + return null; + } + } + return entityPair.getSecond(); + } + + public List listTenants() + { + return getTenantEntities(); + } + + public TenantUpdateEntity getTenantForUpdate(String tenantDomain) + { + TenantEntity entity = getTenantImpl(tenantDomain); + if (entity == null) + { + return null; + } + + // copy for update + TenantUpdateEntity updateEntity = new TenantUpdateEntity(entity.getTenantDomain()); + updateEntity.setVersion(entity.getVersion()); + updateEntity.setEnabled(entity.getEnabled()); + + return updateEntity; + } + + public void updateTenant(TenantUpdateEntity entity) + { + ParameterCheck.mandatory("entity", entity); + ParameterCheck.mandatory("entity.version", entity.getVersion()); + ParameterCheck.mandatoryString("entity.tenantDomain", entity.getTenantDomain()); + + int updated = tenantEntityCache.updateValue(entity.getTenantDomain(), entity); + if (updated < 1) + { + throw new ConcurrencyFailureException("TenantEntity " + entity.getTenantDomain() + " no longer exists or has been updated concurrently"); + } + } + + public void deleteTenant(String tenantDomain) + { + ParameterCheck.mandatoryString("tenantDomain", tenantDomain); + + // force lower-case on delete + tenantDomain = tenantDomain.toLowerCase(); + + int deleted = tenantEntityCache.deleteByKey(tenantDomain); + if (deleted < 1) + { + throw new ConcurrencyFailureException("TenantEntity " + tenantDomain + " no longer exists"); + } + } + + /** + * Callback for alf_tenant DAO + */ + private class TenantEntityCallbackDAO implements EntityLookupCallbackDAO + { + private final Pair convertEntityToPair(TenantEntity entity) + { + if (entity == null) + { + return null; + } + else + { + return new Pair(entity.getTenantDomain(), entity); + } + } + + public Serializable getValueKey(TenantEntity value) + { + return null; + } + + public Pair createValue(TenantEntity value) + { + TenantEntity entity = createTenantEntity(value); + return convertEntityToPair(entity); + } + + public Pair findByKey(String key) + { + TenantEntity entity = getTenantEntity(key); + return convertEntityToPair(entity); + } + + public Pair findByValue(TenantEntity value) + { + if ((value == null) || (value.getTenantDomain() == null)) + { + throw new AlfrescoRuntimeException("Unexpected: TenantEntity / tenantDomain must not be null"); + } + return convertEntityToPair(getTenantEntity(value.getTenantDomain())); + } + + public int updateValue(String tenantDomain, TenantEntity value) + { + return updateTenantEntity(value); + } + + public int deleteByKey(String tenantDomain) + { + return deleteTenantEntity(tenantDomain); + } + + public int deleteByValue(TenantEntity value) + { + throw new UnsupportedOperationException("deleteByValue"); + } + } + + protected abstract TenantEntity createTenantEntity(TenantEntity tenantEntity); + protected abstract TenantEntity getTenantEntity(String tenantDomain); + protected abstract List getTenantEntities(); + protected abstract int updateTenantEntity(TenantEntity tenantEntity); + protected abstract int deleteTenantEntity(String tenantDomain); +} diff --git a/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAO.java b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAO.java new file mode 100644 index 0000000000..ee65d6bc7f --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAO.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant; + +import java.util.List; + + +/** + * Data abstraction layer for Tenant entities. + * + * @author janv + * @since 4.0 (thor) + */ +public interface TenantAdminDAO +{ + /** + * Create tenant - note: tenant domain must be unique + * + * @param tenantEntity + * @return + */ + TenantEntity createTenant(TenantEntity tenantEntity); + + /** + * Get tenant + * + * @param tenantEntity + * @return + */ + TenantEntity getTenant(String tenantDomain); + + /** + * List tenants + * + * TODO add filter(s) + * + * @param tenantEntity + * @return + */ + List listTenants(); + + /** + * Get tenant for update + * + * @param tenantEntity + * @return + */ + TenantUpdateEntity getTenantForUpdate(String tenantDomain); + + /** + * Update tenant + * + * Note: tenant domain cannot be changed + * + * @param tenantUpdateEntity + */ + void updateTenant(TenantUpdateEntity tenantUpdateEntity); + + /** + * Delete tenant + * + * @param tenantEntity + */ + void deleteTenant(String tenantDomain); +} diff --git a/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java new file mode 100644 index 0000000000..44bc128ad0 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant; + +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see TenantAdminDAO + * + * @author janv + * @since 4.0 (thor) + */ +public class TenantAdminDAOTest extends TestCase +{ + private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private TenantAdminDAO tenantAdminDAO; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + transactionService = serviceRegistry.getTransactionService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + tenantAdminDAO = (TenantAdminDAO)ctx.getBean("tenantAdminDAO"); + } + + private TenantEntity createTenant(final String tenantDomain, final boolean enabled) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public TenantEntity execute() throws Throwable + { + TenantEntity tenantEntity = new TenantEntity(); + tenantEntity.setTenantDomain(tenantDomain); + tenantEntity.setEnabled(enabled); + + return tenantAdminDAO.createTenant(tenantEntity); + } + }; + return txnHelper.doInTransaction(callback, false); + } + + private void deleteTenant(final String tenantDomain) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + tenantAdminDAO.deleteTenant(tenantDomain); + return null; + } + }; + txnHelper.doInTransaction(callback, false); + } + + private void updateTenant(final TenantUpdateEntity tenantUpdateEntity) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + tenantAdminDAO.updateTenant(tenantUpdateEntity); + return null; + } + }; + txnHelper.doInTransaction(callback, false); + } + + private TenantEntity getTenant(final String tenantDomain) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public TenantEntity execute() throws Throwable + { + return tenantAdminDAO.getTenant(tenantDomain); + } + }; + return txnHelper.doInTransaction(callback, true); + } + + private TenantUpdateEntity getTenantForUpdate(final String tenantDomain) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public TenantUpdateEntity execute() throws Throwable + { + return tenantAdminDAO.getTenantForUpdate(tenantDomain); + } + }; + return txnHelper.doInTransaction(callback, true); + } + + private List listTenants() throws Exception + { + RetryingTransactionCallback> callback = new RetryingTransactionCallback>() + { + public List execute() throws Throwable + { + return tenantAdminDAO.listTenants(); + } + }; + return txnHelper.doInTransaction(callback, true); + } + + public void testCreateAndDeleteTenant() throws Exception + { + final String tenantDomain = getName() + "-" + System.currentTimeMillis(); + + TenantEntity tenantEntity= getTenant(tenantDomain); + assertNull(tenantEntity); + + TenantEntity createTenantEntity = createTenant(tenantDomain, false); + assertNotNull(createTenantEntity); + + tenantEntity= getTenant(tenantDomain); + assertEquals(createTenantEntity, tenantEntity); + + deleteTenant(tenantDomain); + + assertNull(getTenant(tenantDomain)); + } + + public void testCreateTenantWithRollback() throws Exception + { + final String tenantDomain = getName() + "-" + System.currentTimeMillis(); + + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + createTenant(tenantDomain, false); + // Now force a rollback + throw new RuntimeException("Forced"); + } + }; + + try + { + txnHelper.doInTransaction(callback); + fail("Transaction didn't roll back"); + } + catch (RuntimeException e) + { + // Expected + } + + // Check that it doesn't exist + assertNull(getTenant(tenantDomain)); + } + + public void testUpdateTenant() throws Exception + { + final String tenantDomain = getName() + "-" + System.currentTimeMillis(); + + TenantEntity tenantEntity = getTenant(tenantDomain); + assertNull(tenantEntity); + + TenantEntity createTenantEntity = createTenant(tenantDomain, false); + assertNotNull(createTenantEntity); + assertFalse(createTenantEntity.getEnabled()); + + TenantUpdateEntity tenantUpdateEntity = getTenantForUpdate(tenantDomain); + assertEquals(createTenantEntity, tenantUpdateEntity); + assertFalse(tenantUpdateEntity.getEnabled()); + + tenantUpdateEntity.setEnabled(true); + updateTenant(tenantUpdateEntity); + + tenantEntity = getTenant(tenantDomain); + assertNotNull(tenantEntity); + assertTrue(tenantEntity.getEnabled()); + + deleteTenant(tenantDomain); + + assertNull(getTenant(tenantDomain)); + } + + public void testListTenants() throws Exception + { + final String tenantDomainPrefix = getName() + "-" + System.currentTimeMillis(); + final int cnt = 5; + + int beforeCnt = listTenants().size(); + + for (int i = 1; i <= cnt; i++) + { + String tenantDomain = tenantDomainPrefix + "-" + i; + TenantEntity tenantEntity = getTenant(tenantDomain); + assertNull(tenantEntity); + + tenantEntity = createTenant(tenantDomain, false); + assertNotNull(tenantEntity); + + assertEquals(i+beforeCnt, listTenants().size()); + + tenantEntity = getTenant(tenantDomain); + assertNotNull(tenantEntity); + } + + for (int i = cnt; i >= 1; i--) + { + String tenantDomain = tenantDomainPrefix + "-" + i; + TenantEntity tenantEntity = getTenant(tenantDomain); + assertNotNull(tenantEntity); + + deleteTenant(tenantDomain); + + assertEquals(i-1+beforeCnt, listTenants().size()); + + tenantEntity = getTenant(tenantDomain); + assertNull(tenantEntity); + } + } +} diff --git a/source/java/org/alfresco/repo/domain/tenant/TenantEntity.java b/source/java/org/alfresco/repo/domain/tenant/TenantEntity.java new file mode 100644 index 0000000000..14c754f3a4 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/TenantEntity.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant; + +import org.alfresco.util.EqualsHelper; + + +/** + * Entity for alf_tenant persistence. + * + * @author janv + * @since 4.0 (thor) + */ +public class TenantEntity +{ + private Long version; + private String tenantDomain; + private String tenantName; + private Boolean enabled; + private String contentRoot; // root folder path or url + private String dbUrl; + + /** + * Default constructor + */ + /* package */ TenantEntity() + { + } + + public TenantEntity(String tenantDomain) + { + this.tenantDomain = tenantDomain; + } + + public Long getVersion() + { + return version; + } + + public void setVersion(Long version) + { + this.version = version; + } + + public void incrementVersion() + { + if (this.version >= Long.MAX_VALUE) + { + this.version = 0L; + } + else + { + this.version++; + } + } + + public String getTenantDomain() + { + return tenantDomain; + } + + /* package */ void setTenantDomain(String tenantDomain) + { + this.tenantDomain = tenantDomain; + } + + public String getTenantName() + { + return tenantName; + } + + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public String getContentRoot() + { + return contentRoot; + } + + public void setContentRoot(String contentRoot) + { + this.contentRoot = contentRoot; + } + + public String getDbUrl() + { + return dbUrl; + } + + public void setDbUrl(String dbUrl) + { + this.dbUrl = dbUrl; + } + + + @Override + public int hashCode() + { + return (tenantDomain == null ? 0 : tenantDomain.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj instanceof TenantEntity) + { + TenantEntity that = (TenantEntity)obj; + return (EqualsHelper.nullSafeEquals(this.tenantDomain.toLowerCase(), that.tenantDomain.toLowerCase())); + } + else + { + return false; + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("TenantEntity") + .append("[ tenantDomain=").append(tenantDomain) + .append(", version=").append(version) + .append(", enabled=").append(enabled) + .append(", contentRoot=").append(contentRoot) + .append(", dbUrl=").append(dbUrl) + .append(", tenantName=").append(tenantName) + .append("]"); + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/domain/tenant/TenantUpdateEntity.java b/source/java/org/alfresco/repo/domain/tenant/TenantUpdateEntity.java new file mode 100644 index 0000000000..7cd19b46bd --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/TenantUpdateEntity.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant; + + +/** + * Entity for alf_tenant update. + * + * @author janv + * @since 4.0 (thor) + */ +public class TenantUpdateEntity extends TenantEntity +{ + public TenantUpdateEntity(String tenantDomain) + { + super(tenantDomain); + } + + @Override + public void setTenantDomain(String tenantDomain) + { + throw new UnsupportedOperationException("Cannot update tenantDomain: "+getTenantDomain()); + } +} diff --git a/source/java/org/alfresco/repo/domain/tenant/ibatis/TenantAdminDAOImpl.java b/source/java/org/alfresco/repo/domain/tenant/ibatis/TenantAdminDAOImpl.java new file mode 100644 index 0000000000..3b53b02bbf --- /dev/null +++ b/source/java/org/alfresco/repo/domain/tenant/ibatis/TenantAdminDAOImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.tenant.ibatis; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.domain.tenant.AbstractTenantAdminDAOImpl; +import org.alfresco.repo.domain.tenant.TenantEntity; +import org.mybatis.spring.SqlSessionTemplate; + +/** + * iBatis-specific implementation of the TenantAdmin DAO. + * + * @author janv + * @since 4.0 (thor) + */ +public class TenantAdminDAOImpl extends AbstractTenantAdminDAOImpl +{ + private static final String INSERT_TENANT = "alfresco.tenants.insert_Tenant"; + private static final String SELECT_TENANT = "alfresco.tenants.select_Tenant"; + private static final String SELECT_TENANTS = "alfresco.tenants.select_Tenants"; + private static final String UPDATE_TENANT = "alfresco.tenants.update_Tenant"; + private static final String DELETE_TENANT = "alfresco.tenants.delete_Tenant"; + + private SqlSessionTemplate template; + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + + @Override + protected TenantEntity createTenantEntity(TenantEntity entity) + { + entity.setVersion(0L); + template.insert(INSERT_TENANT, entity); + return entity; + } + + @Override + protected TenantEntity getTenantEntity(String tenantDomain) + { + Map params = new HashMap(1); + params.put("tenantDomain", tenantDomain); + + return (TenantEntity)template.selectOne(SELECT_TENANT, params); + } + + @SuppressWarnings("unchecked") + @Override + protected List getTenantEntities() + { + return (List)template.selectList(SELECT_TENANTS); + } + + @Override + protected int updateTenantEntity(TenantEntity tenantEntity) + { + tenantEntity.incrementVersion(); + + return template.update(UPDATE_TENANT, tenantEntity); + } + + @Override + protected int deleteTenantEntity(String tenantDomain) + { + Map params = new HashMap(1); + params.put("tenantDomain", tenantDomain); + + return template.delete(DELETE_TENANT, params); + } +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java index f24cb82ded..a70225d270 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java @@ -47,6 +47,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.security.PublicServiceAccessService; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -103,6 +104,8 @@ public class AbstractPermissionTest extends TestCase protected PermissionServiceImpl permissionServiceImpl; + protected PublicServiceAccessService publicServiceAccessService; + public AbstractPermissionTest() { super(); @@ -138,6 +141,8 @@ public class AbstractPermissionTest extends TestCase nodeDAO = (NodeDAO) applicationContext.getBean("nodeDAO"); aclDaoComponent = (AclDAO) applicationContext.getBean("aclDAO"); + publicServiceAccessService = (PublicServiceAccessService) applicationContext.getBean("publicServiceAccessService"); + retryingTransactionHelper = (RetryingTransactionHelper) applicationContext.getBean("retryingTransactionHelper"); transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index e48fd052a9..86334abd02 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -64,6 +64,14 @@ public class PermissionServiceTest extends AbstractPermissionTest // TODO Auto-generated constructor stub } + public void testPublicAccessService() + { + runAs("admin"); + assertTrue(publicServiceAccessService.hasAccess("PermissionService", "getAllSetPermissions", rootNodeRef) == AccessStatus.ALLOWED); + assertTrue(publicServiceAccessService.hasAccess("SiteService", "createSite", "", "", "", "", true) == AccessStatus.ALLOWED); + + } + public void testAnyDenyDeniesAndRead() { personService.getPerson("andy"); diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index d48fdb3fde..b65826a292 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -20,13 +20,9 @@ package org.alfresco.repo.tenant; import java.io.File; import java.io.PrintWriter; -import java.io.Serializable; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; @@ -38,6 +34,9 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.admin.RepoModelDefinition; import org.alfresco.repo.content.TenantRoutingFileContentStore; import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.domain.tenant.TenantAdminDAO; +import org.alfresco.repo.domain.tenant.TenantEntity; +import org.alfresco.repo.domain.tenant.TenantUpdateEntity; import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.repo.security.authentication.AuthenticationContext; @@ -48,8 +47,6 @@ import org.alfresco.repo.thumbnail.ThumbnailRegistry; 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.attributes.AttributeService.AttributeQueryCallback; import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -57,7 +54,6 @@ 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.EqualsHelper; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -82,14 +78,16 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo // 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 AuthenticationContext authenticationContext; + private MultiTServiceImpl tenantService; + + protected TransactionService transactionService; + protected DictionaryComponent dictionaryComponent; + protected TenantAdminDAO tenantAdminDAO; + protected PasswordEncoder passwordEncoder; + protected TenantRoutingFileContentStore tenantFileContentStore; + private ThumbnailRegistry thumbnailRegistry; private WorkflowService workflowService; private RepositoryExporterService repositoryExporterService; @@ -159,9 +157,9 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo this.tenantService = tenantService; } - public void setAttributeService(AttributeService attributeService) + public void setTenantAdminDAO(TenantAdminDAO tenantAdminDAO) { - this.attributeService = attributeService; + this.tenantAdminDAO = tenantAdminDAO; } public void setPasswordEncoder(PasswordEncoder passwordEncoder) @@ -239,7 +237,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); PropertyCheck.mandatory(this, "TransactionService", transactionService); PropertyCheck.mandatory(this, "TenantService", tenantService); - PropertyCheck.mandatory(this, "AttributeService", attributeService); + PropertyCheck.mandatory(this, "TenantAdminDAO", tenantAdminDAO); PropertyCheck.mandatory(this, "PasswordEncoder", passwordEncoder); PropertyCheck.mandatory(this, "TenantFileContentStore", tenantFileContentStore); PropertyCheck.mandatory(this, "WorkflowService", workflowService); @@ -295,15 +293,23 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } } - tenantService.register(this); // callback to refresh tenantStatus cache - userTransaction.commit(); - if (logger.isInfoEnabled()) + if ((enabledCount+disabledCount) == 0) + { + AuthenticationUtil.setMtEnabled(false); // explicitly disable if there are no tenants + } + + if (logger.isInfoEnabled() && ((enabledCount+disabledCount) > 0)) { logger.info(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", enabledCount, disabledCount)); } + else if (logger.isDebugEnabled()) + { + logger.debug(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", + enabledCount, disabledCount)); + } } catch(Throwable e) { @@ -321,6 +327,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { tenantDeployers.clear(); tenantDeployers = null; + AuthenticationUtil.setMtEnabled(false); } /** @@ -334,14 +341,18 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo /** * @see TenantAdminService.createTenant() */ - public void createTenant(String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) + public void createTenant(String tenantDomainIn, final char[] tenantAdminRawPassword, String rootContentStoreDir) { ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); - tenantDomain = getTenantDomain(tenantDomain); - + final String tenantDomain = getTenantDomain(tenantDomainIn); + + AuthenticationUtil.setMtEnabled(true); // in case this is the 1st tenant + + long start = System.currentTimeMillis(); + initTenant(tenantDomain, rootContentStoreDir); - + try { // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" @@ -392,8 +403,11 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { AuthenticationUtil.popAuthentication(); } - - logger.info("Tenant created: " + tenantDomain); + + if (logger.isInfoEnabled()) + { + logger.info("Tenant created: " + tenantDomain + " in "+(System.currentTimeMillis()-start)+ " ms"); + } } /** @@ -470,41 +484,22 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { // Check that all the passed values are not null ParameterCheck.mandatory("tenantDomain", tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); return (getTenantAttributes(tenantDomain) != null); } - private void putTenantAttributes(String tenantDomain, Tenant tenant) - { - Map tenantAttributes = new HashMap(7); - tenantAttributes.put(TENANT_ATTRIBUTE_ENABLED, new Boolean(tenant.isEnabled())); - tenantAttributes.put(TENANT_ATTRIBUTE_ROOT_CONTENT_STORE_DIR, tenant.getRootContentStoreDir()); - - attributeService.setAttribute( - (Serializable) tenantAttributes, - TENANTS_ATTRIBUTE_PATH, tenantDomain); - - // update tenant status cache - ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); - } - - @SuppressWarnings("unchecked") private Tenant getTenantAttributes(String tenantDomain) { - Map tenantAttributes = (Map) attributeService.getAttribute( - TENANTS_ATTRIBUTE_PATH, - tenantDomain); - if (tenantAttributes == null) + TenantEntity tenantEntity = tenantAdminDAO.getTenant(tenantDomain); + if (tenantEntity == null) { return null; } else { - Boolean enabled = (Boolean) tenantAttributes.get(TENANT_ATTRIBUTE_ENABLED); - String storeDir = (String) tenantAttributes.get(TENANT_ATTRIBUTE_ROOT_CONTENT_STORE_DIR); - Tenant tenant = new Tenant(tenantDomain, enabled.booleanValue(), storeDir); + Tenant tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot()); return tenant; } } @@ -531,9 +526,9 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo // 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); + TenantUpdateEntity tenantUpdateEntity = tenantAdminDAO.getTenantForUpdate(tenantDomain); + tenantUpdateEntity.setEnabled(true); + tenantAdminDAO.updateTenant(tenantUpdateEntity); if (notifyTenantDeployers) { @@ -592,9 +587,9 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } // 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); + TenantUpdateEntity tenantUpdateEntity = tenantAdminDAO.getTenantForUpdate(tenantDomain); + tenantUpdateEntity.setEnabled(false); + tenantAdminDAO.updateTenant(tenantUpdateEntity); logger.info("Tenant disabled: " + tenantDomain); } @@ -629,13 +624,6 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo 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) { tenantDomain = getTenantDomain(tenantDomain); @@ -659,7 +647,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo throw new AuthenticationException("Tenant does not exist: " + tenantDomain); } else - { + { try { AuthenticationUtil.runAs(new RunAsWork() @@ -706,8 +694,8 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo 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() { @@ -722,12 +710,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo }, getSystemUser(tenantDomain)); // remove tenant - attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); + tenantAdminDAO.deleteTenant(tenantDomain); } catch (Throwable t) { throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); - } + } + + if (logger.isInfoEnabled()) + { + logger.info("Tenant deleted: " + tenantDomain); + } } } @@ -736,32 +729,12 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo */ public List getAllTenants() { - final List tenants = new ArrayList(); - - AttributeQueryCallback callback = new AttributeQueryCallback() + List tenantEntities = tenantAdminDAO.listTenants(); + List tenants = new ArrayList(tenantEntities.size()); + for (TenantEntity tenantEntity : tenantEntities) { - @SuppressWarnings("unchecked") - public boolean handleAttribute(Long id, Serializable value, Serializable[] keys) - { - if (keys.length != 3 || !EqualsHelper.nullSafeEquals(keys[0], TENANTS_ATTRIBUTE_PATH) || keys[1] == null) - { - logger.warn("Unexpected tenant attribute: \n" + - " id: " + id + "\n" + - " keys: " + Arrays.toString(keys) + "\n" + - " value: " + value); - return true; - } - String tenantDomain = (String) keys[1]; - Map tenantAttributes = (Map) value; - Boolean enabled = (Boolean) tenantAttributes.get(TENANT_ATTRIBUTE_ENABLED); - String storeDir = (String) tenantAttributes.get(TENANT_ATTRIBUTE_ROOT_CONTENT_STORE_DIR); - Tenant tenant = new Tenant(tenantDomain, enabled.booleanValue(), storeDir); - tenants.add(tenant); - // Continue - return true; - } - }; - attributeService.getAttributes(callback, TENANTS_ATTRIBUTE_PATH); + tenants.add(new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot())); + } return tenants; } @@ -771,13 +744,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo 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); } @@ -787,12 +760,12 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); StoreRef tenantBootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); systemImporterBootstrap.setStoreUrl(tenantBootstrapStoreRef.toString()); - - // override default property (workspace://SpacesStore) + + // 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) @@ -813,7 +786,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); userImporterBootstrap.setBootstrapViews(bootstrapViews); - + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); } @@ -963,22 +936,22 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo 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(); + { + UserTransaction userTransaction = transactionService.getUserTransaction(); authenticationContext.setSystemUserAsCurrentUser(); - - List tenants = null; + + List tenants = null; try { - userTransaction.begin(); - tenants = getAllTenants(); + userTransaction.begin(); + tenants = getAllTenants(); userTransaction.commit(); } catch(Throwable e) @@ -991,7 +964,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { authenticationContext.clearCurrentSecurityContext(); } - + for (Tenant tenant : tenants) { if (tenant.isEnabled()) @@ -1190,8 +1163,11 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } // init - need to enable tenant (including tenant service) before stores bootstrap - Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); - putTenantAttributes(tenantDomain, tenant); + TenantEntity tenantEntity = new TenantEntity(tenantDomain); + tenantEntity.setEnabled(true); + tenantEntity.setContentRoot(rootContentStoreDir); + + tenantAdminDAO.createTenant(tenantEntity); } private void validateTenantName(String tenantDomain) diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 6fd6b63d6a..adeacd4049 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -79,7 +79,7 @@ public class MultiTDemoTest extends TestCase { private static Log logger = LogFactory.getLog(MultiTDemoTest.class); - private static ApplicationContext ctx =new ClassPathXmlApplicationContext( + private static ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:tenant/mt-*context.xml"} ); @@ -563,10 +563,12 @@ public class MultiTDemoTest extends TestCase { public Object doWork() throws Exception { - Set personRefs = personService.getAllPeople(); + List persons = personService.getPeople(null, true, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage(); - for (NodeRef personRef : personRefs) + for (PersonInfo person : persons) { + NodeRef personRef = person.getNodeRef(); + String userName = (String)nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); assertTrue(userName.endsWith(tenantDomain)); @@ -578,11 +580,11 @@ public class MultiTDemoTest extends TestCase if (tenantDomain.equals(TEST_TENANT_DOMAIN2)) { - assertEquals(5, personRefs.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant, eve@tenant + assertEquals(5, persons.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant, eve@tenant } else { - assertEquals(4, personRefs.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant + assertEquals(4, persons.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant } return null; @@ -1217,15 +1219,16 @@ public class MultiTDemoTest extends TestCase } // pseudo cleanup - if this test runs last - public void testDeleteTenants() + public void testDeleteAllTenants() { - logger.info("test delete tenant"); + logger.info("test delete tenants"); AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - for (final String tenantDomain : tenants) + List allTenants = tenantAdminService.getAllTenants(); + for (final Tenant tenant : allTenants) { - deleteTenant(tenantDomain); + deleteTenant(tenant.getTenantDomain()); } } diff --git a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptorTest.java b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptorTest.java index 37e1412bc1..03b91b58da 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptorTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -24,7 +24,6 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; /** @@ -59,7 +58,7 @@ public class MultiTNodeServiceInterceptorTest extends TestCase enableTest = false; return; } - + // Create a tenant RetryingTransactionCallback createTenantCallback = new RetryingTransactionCallback() { @@ -71,7 +70,28 @@ public class MultiTNodeServiceInterceptorTest extends TestCase }; transactionService.getRetryingTransactionHelper().doInTransaction(createTenantCallback, false, true); } - + + @Override + public void tearDown() throws Exception + { + // If MT is disabled, then disable all tests + if (!tenantAdminService.isEnabled()) + { + return; + } + + // Delete a tenant + RetryingTransactionCallback createTenantCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + tenantAdminService.deleteTenant(tenant1); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(createTenantCallback, false, true); + } + /** * Control case. */ diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 46c63fd7e9..af94c2d7f5 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -21,7 +21,8 @@ package org.alfresco.repo.tenant; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.domain.tenant.TenantAdminDAO; +import org.alfresco.repo.domain.tenant.TenantEntity; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -31,8 +32,6 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.ParameterCheck; @@ -42,18 +41,12 @@ import org.springframework.extensions.surf.util.ParameterCheck; * Adapts names to be tenant specific or vice-versa. */ public class MultiTServiceImpl implements TenantService -{ - private static Log logger = LogFactory.getLog(MultiTServiceImpl.class); +{ + private TenantAdminDAO tenantAdminDAO; - // clusterable cache of enabled/disabled tenants - managed via TenantAdmin Service - private SimpleCache tenantsCache; - - private MultiTAdminServiceImpl tenantAdminService = null; // registered (rather than injected) - to avoid circular dependency - - - public void setTenantsCache(SimpleCache tenantsCache) + public void setTenantAdminDAO(TenantAdminDAO tenantAdminDAO) { - this.tenantsCache = tenantsCache; + this.tenantAdminDAO = tenantAdminDAO; } /* (non-Javadoc) @@ -674,31 +667,18 @@ public class MultiTServiceImpl implements TenantService throw new TenantDisabledException(tenantDomain); } } - + /* (non-Javadoc) * @see org.alfresco.repo.tenant.TenantService#getTenant(java.lang.String) */ public Tenant getTenant(String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); - Tenant tenant = tenantsCache.get(tenantDomain); - if (tenant == null) + TenantEntity tenantEntity = tenantAdminDAO.getTenant(tenantDomain); + Tenant tenant = null; + if (tenantEntity != null) { - // backed by TenantAdminService - update this cache, e.g. could have been invalidated and/or expired - if (tenantAdminService != null) - { - tenant = tenantAdminService.getTenant(tenantDomain); - if (tenant == null) - { - throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); - } - else - { - putTenant(tenantDomain, tenant); - } - } + tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot()); } - return tenant; } @@ -710,32 +690,6 @@ public class MultiTServiceImpl implements TenantService return true; } - // should only be called by Tenant Admin Service - protected void register(MultiTAdminServiceImpl tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - - // should only be called by Tenant Admin Service - protected void putTenant(String tenantDomain, Tenant tenant) - { - if (logger.isDebugEnabled()) - { - logger.debug("putTenant " + tenantDomain); - } - tenantsCache.put(tenantDomain, tenant); - } - - // should only be called by Tenant Admin Service - protected void removeTenant(String tenantDomain) - { - if (logger.isDebugEnabled()) - { - logger.debug("removeTenant " + tenantDomain); - } - tenantsCache.remove(tenantDomain); - } - private String getTenantDomain(String tenantDomain) { return tenantDomain.toLowerCase(I18NUtil.getLocale()); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 1b0ad92cc2..23dce70231 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -127,8 +127,8 @@ import org.springmodules.workflow.jbpm31.JbpmTemplate; */ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { - private static Log logger = LogFactory.getLog(JBPMEngine.class); - + private static Log logger = LogFactory.getLog(JBPMEngine.class); + // Implementation dependencies protected NodeService nodeService; protected ServiceRegistry serviceRegistry; @@ -446,15 +446,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowDefinition apply(ProcessDefinition value) { - try - { - return createWorkflowDefinition(value); - } - catch (Exception ex) - { - logger.warn("Unable to load workflow definition: '" + value + "' due to exception.", ex); - return null; - } + try + { + return createWorkflowDefinition(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow definition: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -465,15 +465,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { public WorkflowInstance apply(ProcessInstance value) { - try - { - return createWorkflowInstance(value); - } - catch (Exception ex) - { - logger.warn("Unable to load workflow instance: '" + value + "' due to exception.", ex); - return null; - } + try + { + return createWorkflowInstance(value); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + value + "' due to exception.", ex); + return null; + } } }); } @@ -913,15 +913,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // retrieve workflow GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstanceIfExists(graphSession, workflowId); - try - { - return createWorkflowInstance(processInstance); - } - catch (Exception ex) - { - logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); - return null; - } + try + { + return createWorkflowInstance(processInstance); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + return null; + } } }); } @@ -1132,14 +1132,14 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine ProcessInstance processInstance = processInstances.get(workflowId); // TODO: Determine if this is the most appropriate way to cancel workflow... // It might be useful to record point at which it was cancelled etc - try - { - workflowInstances.add(createWorkflowInstance(processInstance)); - } - catch(Exception ex) - { - logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); - } + try + { + workflowInstances.add(createWorkflowInstance(processInstance)); + } + catch(Exception ex) + { + logger.warn("Unable to load workflow instance: '" + processInstance + "' due to exception.", ex); + } // delete the process instance graphSession.deleteProcessInstance(processInstance, true, true); @@ -1549,20 +1549,20 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /// ------------------------ for(Object[] row : rows) { - try + try { - WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); - if(workflowTask != null) - { - workflowTasks.add(workflowTask); - } + WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); + if(workflowTask != null) + { + workflowTasks.add(workflowTask); + } } - catch (Exception ex) - { - logger.warn("Unable to load workflow instance: '" + row[0] + "' due to exception.", ex); - continue; - } - } + catch (Exception ex) + { + logger.warn("Unable to load workflow instance: '" + row[0] + "' due to exception.", ex); + continue; + } + } return workflowTasks; } @@ -1593,7 +1593,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine TaskInstance helperTi = taskInstanceCache.get(ti.getId()); @SuppressWarnings("unchecked") - Map variables = variablesCache.get(contextInstance.getId()).getVariables(); + Map variables = variablesCache.get(contextInstance.getTokenVariableMap(token).getToken().getId()).getVariables(); // WorkflowTaskProperies Map properties = getTaskProperties(helperTi != null ? helperTi : ti, false, variablesCache); @@ -1640,7 +1640,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine List results = (List) query.list(); for (TokenVariableMap tokenVariableMap : results) { - variablesCache.put(tokenVariableMap.getContextInstance().getId(), tokenVariableMap); + variablesCache.put(tokenVariableMap.getToken().getId(), tokenVariableMap); } } @@ -1817,16 +1817,16 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine workflowTasks = new ArrayList(filteredTasks.size()); for (TaskInstance task : filteredTasks) { - try - { - WorkflowTask workflowTask = createWorkflowTask(task); - workflowTasks.add(workflowTask); - } - catch (Exception ex) - { - logger.warn("Unable to load workflow task: '" + task + "' due to exception.", ex); - continue; - } + try + { + WorkflowTask workflowTask = createWorkflowTask(task); + workflowTasks.add(workflowTask); + } + catch (Exception ex) + { + logger.warn("Unable to load workflow task: '" + task + "' due to exception.", ex); + continue; + } } } @@ -2676,9 +2676,9 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine while (token != null) { TokenVariableMap varMap = null; - if (variablesCache != null && variablesCache.containsKey(context.getId())) + if (variablesCache != null && variablesCache.containsKey(context.getTokenVariableMap(token).getToken().getId())) { - varMap = variablesCache.get(context.getId()); + varMap = variablesCache.get(context.getTokenVariableMap(token).getToken().getId()); } else { diff --git a/source/test-resources/tenant/mt-contentstore-context.xml b/source/test-resources/tenant/mt-contentstore-context.xml index 6fc64c92ca..ff961edc34 100644 --- a/source/test-resources/tenant/mt-contentstore-context.xml +++ b/source/test-resources/tenant/mt-contentstore-context.xml @@ -24,7 +24,7 @@ - + ${dir.contentstore} @@ -33,7 +33,7 @@ - + diff --git a/source/test-resources/tenant/mt-context.xml b/source/test-resources/tenant/mt-context.xml index 90645167f8..340ea89fc2 100644 --- a/source/test-resources/tenant/mt-context.xml +++ b/source/test-resources/tenant/mt-context.xml @@ -2,49 +2,15 @@ - - - - - - - - - - - - - - org.alfresco.cache.tenantsCache - - - - - - - - - - - - - org.alfresco.tenantsTransactionalCache - - - 10 - - - - - + - - + + - - + +