From 2e826e9e26f006f4d9999457bb384e9342ea2a31 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Thu, 19 Apr 2012 15:43:03 +0000 Subject: [PATCH] Merged V4.0-BUG-FIX to HEAD 35394: ALF-13394: Allow administrators to gain SiteManager role 35407: ALF-12740: Updated XHR request processing for IE to ensure no caching (had previously thought IE9 was not affected but apparently it is) 35408: Fix for ALF-13286 who_can_create_site mention and bad error message - explicitly declare public interface on SiteService 35412: Merged DEV to V4.0-BUG-FIX 35153: ALF-1834: Renaming folders breaks web form content Original solution by Ivan has been reimplemented as per Derek's requirements and against 4.0. [DH: Did some minor legibility changes and fixed LF for AVMDiskDriver] 35414: Incremented version.revision for 4.0.2 35417: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/V4.0-BUG-FIX: (THOR-6 / ALF-13755) 29356: THOR-6: MT is configured (but not enabled) by default - will be auto-enabled when first tenant is created 29455: THOR-6: build test/fix 29471: THOR-6: build test/fix 35423: Merged BRANCHES/DEV/THOR0 to BRANCHES/DEV/V4.0-BUG-FIX: (THOR-4 / ALF-13756) 29500: THOR-4: Replace Tenant attributes with Tenant table (alf_tenant) 29501: THOR-4: Replace Tenant attributes with Tenant table (alf_tenant) 29503: THOR-4: Replace Tenant attributes with Tenant table (alf_tenant) TODO: ALF-13757 - patch to migrate Tenant attributes to Tenant table (for existing customers) 35431: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 35367: ALF-13382: WorkflowService Returning Incorrect Values When Using Native jBPM API to set Local Task Variables - Fix by Alex Bykov 35400: Fix for ALF-13557 - Share forms selectmany.ftl template does not work with Share search query parser; should do a logical OR or AND but does a concatenation 35416: Incremented version.revision for 3.4.10 35429: Merged V4.0-BUG-FIX to V3.4-BUG-FIX (partial) 35328: ALF-13409: Avoid concurrency issues in unit test tear downs by deleting users before sites. User deletion deletes invitations synchronously. Site deletion deletes invitations concurrently to avoid UI timeouts. The potential to access invitations that are being concurrently deleted still exists, but always did! 35430: ALF-13409: Avoid concurrency issues in unit test tear downs by deleting users before sites - another instance. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35434 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/avm-services-context.xml | 19 +- config/alfresco/bootstrap-context.xml | 5 +- config/alfresco/cache-context.xml | 33 + config/alfresco/dao/dao-context.xml | 5 + .../AlfrescoCreate-TenantTables.sql | 30 + .../AlfrescoCreate-TenantTables.sql | 30 + config/alfresco/ehcache-default.xml | 4 +- .../extension/mt/mt-admin-context.xml | 38 + .../extension/mt/mt-admin-context.xml.sample | 22 +- .../extension/mt/mt-contentstore-context.xml | 62 + .../mt/mt-contentstore-context.xml.sample | 59 +- config/alfresco/extension/mt/mt-context.xml | 52 + .../extension/mt/mt-context.xml.sample | 46 +- .../alfresco/ibatis/alfresco-SqlMapConfig.xml | 5 + .../tenants-common-SqlMap.xml | 76 + config/alfresco/mt/mt-base-context.xml | 2 +- .../alfresco/patch/patch-services-context.xml | 13 +- config/alfresco/repository.properties | 3 + config/alfresco/site-services-context.xml | 5 +- config/alfresco/version.properties | 4 +- .../alfresco/filesys/avm/AVMDiskDriver.java | 6689 +++++++++-------- .../org/alfresco/repo/avm/AVMNodeService.java | 17 +- .../alfresco/repo/avm/LayeredFolderType.java | 332 + .../TenantRoutingFileContentStore.java | 10 +- .../repo/descriptor/DescriptorStartupLog.java | 15 +- .../alfresco/repo/domain/DomainTestSuite.java | 4 +- .../tenant/AbstractTenantAdminDAOImpl.java | 231 + .../repo/domain/tenant/TenantAdminDAO.java | 81 + .../domain/tenant/TenantAdminDAOTest.java | 243 + .../repo/domain/tenant/TenantEntity.java | 162 + .../domain/tenant/TenantUpdateEntity.java | 40 + .../tenant/ibatis/TenantAdminDAOImpl.java | 91 + .../impl/AbstractPermissionTest.java | 5 + .../impl/PermissionServiceTest.java | 8 + .../repo/tenant/MultiTAdminServiceImpl.java | 196 +- .../alfresco/repo/tenant/MultiTDemoTest.java | 21 +- .../MultiTNodeServiceInterceptorTest.java | 28 +- .../repo/tenant/MultiTServiceImpl.java | 68 +- .../repo/workflow/jbpm/JBPMEngine.java | 126 +- .../tenant/mt-contentstore-context.xml | 4 +- source/test-resources/tenant/mt-context.xml | 44 +- 41 files changed, 5164 insertions(+), 3764 deletions(-) create mode 100644 config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-TenantTables.sql create mode 100644 config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-TenantTables.sql create mode 100644 config/alfresco/extension/mt/mt-admin-context.xml create mode 100644 config/alfresco/extension/mt/mt-contentstore-context.xml create mode 100644 config/alfresco/extension/mt/mt-context.xml create mode 100644 config/alfresco/ibatis/org.hibernate.dialect.Dialect/tenants-common-SqlMap.xml create mode 100644 source/java/org/alfresco/repo/avm/LayeredFolderType.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/AbstractTenantAdminDAOImpl.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/TenantAdminDAO.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/TenantEntity.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/TenantUpdateEntity.java create mode 100644 source/java/org/alfresco/repo/domain/tenant/ibatis/TenantAdminDAOImpl.java 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 - - - - - + - - + + - - + +