diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index 518779a809..f335103458 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -332,6 +332,9 @@ + + + diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index e610ce2e7a..dd17c8b969 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -147,9 +147,6 @@ - - - diff --git a/config/alfresco/template-services-context.xml b/config/alfresco/template-services-context.xml index 7887d0c3cf..2768b21cc4 100644 --- a/config/alfresco/template-services-context.xml +++ b/config/alfresco/template-services-context.xml @@ -46,9 +46,6 @@ - - - diff --git a/config/alfresco/wcm-services-context.xml b/config/alfresco/wcm-services-context.xml index b68ddefdaf..3c25e90d56 100644 --- a/config/alfresco/wcm-services-context.xml +++ b/config/alfresco/wcm-services-context.xml @@ -3,6 +3,8 @@ + + @@ -49,22 +51,90 @@ - + + + + + + + + org.alfresco.wcm.sandbox.SandboxService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + webprojects - - - - - - + diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java index 5ce429af86..ae2b609d60 100644 --- a/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java @@ -44,7 +44,7 @@ import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.workflow.WorkflowModel; -import org.alfresco.sandbox.SandboxConstants; +import org.alfresco.wcm.sandbox.SandboxConstants; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; @@ -66,9 +66,8 @@ import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.DNSNameMangler; -import org.alfresco.util.GUID; import org.alfresco.util.Pair; +import org.alfresco.wcm.sandbox.SandboxFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -97,6 +96,7 @@ public class AVMExpiredContentProcessor protected TransactionService transactionService; protected VirtServerRegistry virtServerRegistry; protected SearchService searchService; + private SandboxFactory sandboxFactory; private static Log logger = LogFactory.getLog(AVMExpiredContentProcessor.class); @@ -171,6 +171,11 @@ public class AVMExpiredContentProcessor { this.avmLockingAwareService = avmLockingAwareService; } + + public void setSandboxFactory(SandboxFactory sandboxFactory) + { + this.sandboxFactory = sandboxFactory; + } /** * Executes the expired content processor. @@ -425,7 +430,7 @@ public class AVMExpiredContentProcessor NodeRef assignee = this.personService.getPerson(userName); // create a workflow store layered over the users store - String workflowStoreName = createUserWorkflowSandbox(storeName, userStore); + String workflowStoreName = sandboxFactory.createUserWorkflowSandbox(storeName, userStore); // create a workflow package with all the expired items NodeRef workflowPackage = setupWorkflowPackage(workflowStoreName, expiredContent); @@ -453,124 +458,6 @@ public class AVMExpiredContentProcessor } } } - - /** - * Creates a workflow sandbox for the given user store. This will create a - * workflow sandbox layered over the user's main store. - * - * @param stagingStore The name of the staging store the user sandbox is layered over - * @param userStore The name of the user store to create the workflow for - * @return The store name of the main store in the workflow sandbox - */ - private String createUserWorkflowSandbox(String stagingStore, String userStore) - { - // create the workflow 'main' store - String packageName = "workflow-" + GUID.generate(); - String workflowStoreName = userStore + STORE_SEPARATOR + packageName; - - this.avmService.createStore(workflowStoreName); - - if (logger.isDebugEnabled()) - logger.debug("Created user workflow sandbox store: " + workflowStoreName); - - // create a layered directory pointing to 'www' in the users store - this.avmService.createLayeredDirectory( - userStore + ":/" + JNDIConstants.DIR_DEFAULT_WWW, - workflowStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); - - // tag the store with the store type - this.avmService.setStoreProperty(workflowStoreName, - SandboxConstants.PROP_SANDBOX_AUTHOR_WORKFLOW_MAIN, - new PropertyValue(DataTypeDefinition.TEXT, null)); - - // tag the store with the name of the author's store this one is layered over - this.avmService.setStoreProperty(workflowStoreName, - SandboxConstants.PROP_AUTHOR_NAME, - new PropertyValue(DataTypeDefinition.TEXT, userStore)); - - // tag the store, oddly enough, with its own store name for querying. - this.avmService.setStoreProperty(workflowStoreName, - QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + workflowStoreName), - new PropertyValue(DataTypeDefinition.TEXT, null)); - - // tag the store with the DNS name property - String path = workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + - "/" + JNDIConstants.DIR_DEFAULT_APPBASE; - // DNS name mangle the property name - can only contain value DNS characters! - String dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(stagingStore, packageName); - this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, dnsProp), - new PropertyValue(DataTypeDefinition.TEXT, path)); - - // the main workflow store depends on the main user store (dist=1) - String prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; - this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), - new PropertyValue(DataTypeDefinition.INT, 1)); - - // The main workflow store depends on the main staging store (dist=2) - prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; - this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), - new PropertyValue(DataTypeDefinition.INT, 2)); - - // snapshot the store - this.avmService.createSnapshot(workflowStoreName, null, null); - - // create the workflow 'preview' store - String previewStoreName = workflowStoreName + STORE_SEPARATOR + "preview"; - this.avmService.createStore(previewStoreName); - - if (logger.isDebugEnabled()) - logger.debug("Created user workflow sandbox preview store: " + previewStoreName); - - // create a layered directory pointing to 'www' in the workflow 'main' store - this.avmService.createLayeredDirectory( - workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW, - previewStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); - - // tag the store with the store type - this.avmService.setStoreProperty(previewStoreName, SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW, - new PropertyValue(DataTypeDefinition.TEXT, null)); - - // tag the store with its own store name for querying. - avmService.setStoreProperty(previewStoreName, - QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + previewStoreName), - new PropertyValue(DataTypeDefinition.TEXT, null)); - - // tag the store with the DNS name property - path = previewStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + - "/" + JNDIConstants.DIR_DEFAULT_APPBASE; - // DNS name mangle the property name - can only contain value DNS characters! - dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(userStore, packageName, "preview"); - this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, dnsProp), - new PropertyValue(DataTypeDefinition.TEXT, path)); - - // The preview worfkflow store depends on the main workflow store (dist=1) - prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + workflowStoreName; - this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), - new PropertyValue(DataTypeDefinition.INT, 1)); - - // The preview workflow store depends on the main user store (dist=2) - prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; - this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), - new PropertyValue(DataTypeDefinition.INT, 2)); - - // The preview workflow store depends on the main staging store (dist=3) - prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; - this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), - new PropertyValue(DataTypeDefinition.INT, 3)); - - // snapshot the store - this.avmService.createSnapshot(previewStoreName, null, null); - - // tag all related stores to indicate that they are part of a single sandbox - QName sandboxIdProp = QName.createQName(SandboxConstants.PROP_SANDBOXID + GUID.generate()); - this.avmService.setStoreProperty(workflowStoreName, sandboxIdProp, - new PropertyValue(DataTypeDefinition.TEXT, null)); - this.avmService.setStoreProperty(previewStoreName, sandboxIdProp, - new PropertyValue(DataTypeDefinition.TEXT, null)); - - // return the main workflow store name - return workflowStoreName; - } /** * Sets up a workflow package from the given main workflow store and applies diff --git a/source/java/org/alfresco/repo/jscript/AVM.java b/source/java/org/alfresco/repo/jscript/AVM.java index f084d40317..51bab1342a 100644 --- a/source/java/org/alfresco/repo/jscript/AVM.java +++ b/source/java/org/alfresco/repo/jscript/AVM.java @@ -24,22 +24,17 @@ */ package org.alfresco.repo.jscript; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.alfresco.config.JNDIConstants; import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; -import org.alfresco.service.cmr.avmsync.AVMDifference; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.NameMatcher; import org.alfresco.util.ParameterCheck; +import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.util.WCMUtil; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; @@ -52,8 +47,6 @@ public final class AVM extends BaseScopableProcessorExtension { /** Repository Service Registry */ private ServiceRegistry services; - - private NameMatcher matcher; /** * Set the service registry @@ -64,11 +57,6 @@ public final class AVM extends BaseScopableProcessorExtension { this.services = serviceRegistry; } - - public void setNameMatcher(NameMatcher matcher) - { - this.matcher = matcher; - } /** * @return a array of all AVM stores in the system @@ -163,28 +151,17 @@ public final class AVM extends BaseScopableProcessorExtension ParameterCheck.mandatoryString("Username", username); ParameterCheck.mandatoryString("Webapp", webapp); - List items; - - AVMService avmService = this.services.getAVMService(); - - // build the paths to the stores to compare - filter by current webapp - String userStore = userSandboxStore(storeId, username); - String userStorePath = getStoreRootWebappPath(userStore, webapp); - String stagingStore = stagingStore(storeId); - String stagingStorePath = getStoreRootWebappPath(stagingStore, webapp); + SandboxService sbService = this.services.getSandboxService(); - List diffs = this.services.getAVMSyncService().compare( - -1, userStorePath, -1, stagingStorePath, this.matcher); - items = new ArrayList(diffs.size()); - for (AVMDifference diff : diffs) + // get modified items - not including deleted + List nodes = sbService.listChangedItemsWebApp(storeId, webapp, false); + + List items = new ArrayList(nodes.size()); + + for (AVMNodeDescriptor node : nodes) { - // convert each diff record into an AVM Node template wrapper - String sourcePath = diff.getSourcePath(); - AVMNodeDescriptor node = avmService.lookup(-1, sourcePath); - if (node != null) - { - items.add(new AVMNode(node.getPath(), -1, this.services, getScope())); - } + // convert each diff/node record into an AVM Node wrapper + items.add(new AVMNode(node.getPath(), -1, this.services, getScope())); } return items; @@ -197,8 +174,7 @@ public final class AVM extends BaseScopableProcessorExtension */ public static String stagingStore(String storeId) { - ParameterCheck.mandatoryString("Store ID", storeId); - return storeId; + return WCMUtil.buildStagingStoreName(storeId); } /** @@ -209,9 +185,7 @@ public final class AVM extends BaseScopableProcessorExtension */ public static String userSandboxStore(String storeId, String username) { - ParameterCheck.mandatoryString("Store ID", storeId); - ParameterCheck.mandatoryString("Username", username); - return storeId + "--" + username; + return WCMUtil.buildUserMainStoreName(storeId, username); } /** @@ -221,9 +195,7 @@ public final class AVM extends BaseScopableProcessorExtension */ public String websiteStagingUrl(String storeId) { - ParameterCheck.mandatoryString("Store ID", storeId); - return MessageFormat.format(JNDIConstants.PREVIEW_SANDBOX_URL, - lookupStoreDNS(storeId), getVServerDomain(), getVServerPort()); + return WCMUtil.buildStoreUrl(this.services.getAVMService(), storeId, getVServerDomain(), getVServerPort()); } /** @@ -247,25 +219,7 @@ public final class AVM extends BaseScopableProcessorExtension */ public String assetUrl(String store, String assetPath) { - ParameterCheck.mandatoryString("Store", store); - ParameterCheck.mandatoryString("Asset Path", assetPath); - - if (assetPath.startsWith('/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE)) - { - assetPath = assetPath.substring(('/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE).length()); - } - if (assetPath.startsWith("/ROOT")) - { - assetPath = assetPath.substring(("/ROOT").length()); - } - if (assetPath.length() == 0 || assetPath.charAt(0) != '/') - { - assetPath = '/' + assetPath; - } - return MessageFormat.format(JNDIConstants.PREVIEW_ASSET_URL, - lookupStoreDNS(store), getVServerDomain(), getVServerPort(), assetPath); + return WCMUtil.buildAssetUrl(assetPath, getVServerDomain(), getVServerPort(), WCMUtil.lookupStoreDNS(this.services.getAVMService(), store)); } /** @@ -315,27 +269,6 @@ public final class AVM extends BaseScopableProcessorExtension */ public static String getWebappsFolderPath() { - return '/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE; + return JNDIConstants.DIR_DEFAULT_WWW_APPBASE; } - - private static String getStoreRootPath(String store) - { - return store + ":" + getWebappsFolderPath(); - } - - private static String getStoreRootWebappPath(String store, String webapp) - { - return getStoreRootPath(store) + '/' + webapp; - } - - private String lookupStoreDNS(String store) - { - Map props = - this.services.getAVMService().queryStorePropertyKey(store, QName.createQName(null, PROP_DNS + '%')); - return (props.size() == 1 - ? props.keySet().iterator().next().getLocalName().substring(PROP_DNS.length()) : null); - } - - private final static String PROP_DNS = ".dns."; } diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index 496c537c36..13a2872f7f 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -69,6 +69,7 @@ import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.wcm.sandbox.SandboxService; import org.alfresco.wcm.webproject.WebProjectService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -466,6 +467,13 @@ public class ServiceDescriptorRegistry * @see org.alfresco.service.ServiceRegistry#getWebProjectService() */ public WebProjectService getWebProjectService() { - return (WebProjectService) getService(WEBPROJECT_SERVICE); + return (WebProjectService)getService(WEBPROJECT_SERVICE); + } + + /* (non-Javadoc) + * @see org.alfresco.service.ServiceRegistry#getSandboxService() + */ + public SandboxService getSandboxService() { + return (SandboxService)getService(SANDBOX_SERVICE); } } diff --git a/source/java/org/alfresco/repo/template/AVM.java b/source/java/org/alfresco/repo/template/AVM.java index 9f3fe48fa1..1c4498624f 100644 --- a/source/java/org/alfresco/repo/template/AVM.java +++ b/source/java/org/alfresco/repo/template/AVM.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,21 +24,16 @@ */ package org.alfresco.repo.template; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.alfresco.config.JNDIConstants; -import org.alfresco.repo.domain.PropertyValue; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; -import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; -import org.alfresco.service.cmr.avmsync.AVMDifference; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.NameMatcher; import org.alfresco.util.ParameterCheck; +import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.util.WCMUtil; /** * AVM root object access for a template model. @@ -48,7 +43,6 @@ import org.alfresco.util.ParameterCheck; public class AVM extends BaseTemplateProcessorExtension { private ServiceRegistry services; - private NameMatcher matcher; /** * Sets the service registry @@ -60,11 +54,6 @@ public class AVM extends BaseTemplateProcessorExtension this.services = services; } - public void setNameMatcher(NameMatcher matcher) - { - this.matcher = matcher; - } - /** * @return a list of all AVM stores in the system */ @@ -152,28 +141,17 @@ public class AVM extends BaseTemplateProcessorExtension ParameterCheck.mandatoryString("Username", username); ParameterCheck.mandatoryString("Webapp", webapp); - List items; - - AVMService avmService = this.services.getAVMService(); - - // build the paths to the stores to compare - filter by current webapp - String userStore = userSandboxStore(storeId, username); - String userStorePath = getStoreRootWebappPath(userStore, webapp); - String stagingStore = stagingStore(storeId); - String stagingStorePath = getStoreRootWebappPath(stagingStore, webapp); + SandboxService sbService = this.services.getSandboxService(); - List diffs = this.services.getAVMSyncService().compare( - -1, userStorePath, -1, stagingStorePath, this.matcher); - items = new ArrayList(diffs.size()); - for (AVMDifference diff : diffs) + // get modified items - not including deleted + List nodes = sbService.listChangedItemsWebApp(storeId, webapp, false); + + List items = new ArrayList(nodes.size()); + + for (AVMNodeDescriptor node : nodes) { - // convert each diff record into an AVM Node template wrapper - String sourcePath = diff.getSourcePath(); - AVMNodeDescriptor node = avmService.lookup(-1, sourcePath); - if (node != null) - { - items.add(new AVMTemplateNode(node, this.services, getTemplateImageResolver())); - } + // convert each diff/node record into an AVM Node template wrapper + items.add(new AVMTemplateNode(node, this.services, getTemplateImageResolver())); } return items; @@ -186,8 +164,7 @@ public class AVM extends BaseTemplateProcessorExtension */ public static String stagingStore(String storeId) { - ParameterCheck.mandatoryString("Store ID", storeId); - return storeId; + return WCMUtil.buildStagingStoreName(storeId); } /** @@ -198,9 +175,7 @@ public class AVM extends BaseTemplateProcessorExtension */ public static String userSandboxStore(String storeId, String username) { - ParameterCheck.mandatoryString("Store ID", storeId); - ParameterCheck.mandatoryString("Username", username); - return storeId + "--" + username; + return WCMUtil.buildUserMainStoreName(storeId, username); } /** @@ -210,9 +185,7 @@ public class AVM extends BaseTemplateProcessorExtension */ public String websiteStagingUrl(String storeId) { - ParameterCheck.mandatoryString("Store ID", storeId); - return MessageFormat.format(JNDIConstants.PREVIEW_SANDBOX_URL, - lookupStoreDNS(storeId), getVServerDomain(), getVServerPort()); + return WCMUtil.buildStoreUrl(this.services.getAVMService(), storeId, getVServerDomain(), getVServerPort()); } /** @@ -236,25 +209,7 @@ public class AVM extends BaseTemplateProcessorExtension */ public String assetUrl(String store, String assetPath) { - ParameterCheck.mandatoryString("Store", store); - ParameterCheck.mandatoryString("Asset Path", assetPath); - - if (assetPath.startsWith('/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE)) - { - assetPath = assetPath.substring(('/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE).length()); - } - if (assetPath.startsWith("/ROOT")) - { - assetPath = assetPath.substring(("/ROOT").length()); - } - if (assetPath.length() == 0 || assetPath.charAt(0) != '/') - { - assetPath = '/' + assetPath; - } - return MessageFormat.format(JNDIConstants.PREVIEW_ASSET_URL, - lookupStoreDNS(store), getVServerDomain(), getVServerPort(), assetPath); + return WCMUtil.buildAssetUrl(assetPath, getVServerDomain(), getVServerPort(), WCMUtil.lookupStoreDNS(this.services.getAVMService(), store)); } /** @@ -304,27 +259,6 @@ public class AVM extends BaseTemplateProcessorExtension */ public static String getWebappsFolderPath() { - return '/' + JNDIConstants.DIR_DEFAULT_WWW + - '/' + JNDIConstants.DIR_DEFAULT_APPBASE; + return JNDIConstants.DIR_DEFAULT_WWW_APPBASE; } - - private static String getStoreRootPath(String store) - { - return store + ":" + getWebappsFolderPath(); - } - - private static String getStoreRootWebappPath(String store, String webapp) - { - return getStoreRootPath(store) + '/' + webapp; - } - - private String lookupStoreDNS(String store) - { - Map props = - this.services.getAVMService().queryStorePropertyKey(store, QName.createQName(null, PROP_DNS + '%')); - return (props.size() == 1 - ? props.keySet().iterator().next().getLocalName().substring(PROP_DNS.length()) : null); - } - - private final static String PROP_DNS = ".dns."; } diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 8515ffc46e..e5343ef4df 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -33,6 +33,7 @@ import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.deploy.DeploymentService; import org.alfresco.service.cmr.avm.locking.AVMLockingService; import org.alfresco.service.cmr.avmsync.AVMSyncService; import org.alfresco.service.cmr.coci.CheckOutCheckInService; @@ -62,12 +63,12 @@ import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ExporterService; import org.alfresco.service.cmr.view.ImporterService; -import org.alfresco.service.cmr.avm.deploy.DeploymentService; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.wcm.sandbox.SandboxService; import org.alfresco.wcm.webproject.WebProjectService; @@ -129,6 +130,7 @@ public interface ServiceRegistry static final QName TAGGING_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "TaggingService"); static final QName DEPLOYMENT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "DeploymentService"); static final QName WEBPROJECT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "WebProjectService"); + static final QName SANDBOX_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "SandboxService"); /** * Get the list of services provided by the Repository @@ -429,4 +431,11 @@ public interface ServiceRegistry */ @NotAuditable WebProjectService getWebProjectService(); + + /** + * Get the Sandbox Service + * @return + */ + @NotAuditable + SandboxService getSandboxService(); } diff --git a/source/java/org/alfresco/wcm/WCMTestSuite.java b/source/java/org/alfresco/wcm/WCMTestSuite.java new file mode 100644 index 0000000000..169a66060b --- /dev/null +++ b/source/java/org/alfresco/wcm/WCMTestSuite.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.alfresco.wcm.sandbox.SandboxServiceImplTest; +import org.alfresco.wcm.webproject.WebProjectServiceImplTest; + +/** + * WCM test suite + * + * @author janv + */ +public class WCMTestSuite extends TestSuite +{ + /** + * Creates the test suite + * + * @return the test suite + */ + public static Test suite() + { + TestSuite suite = new TestSuite(); + + suite.addTestSuite(WebProjectServiceImplTest.class); + suite.addTestSuite(SandboxServiceImplTest.class); + + return suite; + } +} diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxConstants.java b/source/java/org/alfresco/wcm/sandbox/SandboxConstants.java index 9857eb7d3a..c4afed6536 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxConstants.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxConstants.java @@ -43,6 +43,7 @@ public class SandboxConstants public final static String PROP_DNS = ".dns."; public final static String PROP_SANDBOX_STORE_PREFIX = ".sandbox.store."; + // sandbox type public final static QName PROP_SANDBOX_STAGING_MAIN = QName.createQName(null, ".sandbox.staging.main"); public final static QName PROP_SANDBOX_STAGING_PREVIEW = QName.createQName(null, ".sandbox.staging.preview"); public final static QName PROP_SANDBOX_AUTHOR_MAIN = QName.createQName(null, ".sandbox.author.main"); @@ -51,6 +52,7 @@ public class SandboxConstants public final static QName PROP_SANDBOX_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.workflow.preview"); public final static QName PROP_SANDBOX_AUTHOR_WORKFLOW_MAIN = QName.createQName(null, ".sandbox.author.workflow.main"); public final static QName PROP_SANDBOX_AUTHOR_WORKFLOW_PREVIEW = QName.createQName(null, ".sandbox.author.workflow.preview"); + public final static QName PROP_WEBSITE_NAME = QName.createQName(null, ".website.name"); public final static QName PROP_AUTHOR_NAME = QName.createQName(null, ".author.name"); public final static QName PROP_WEB_PROJECT_NODE_REF = QName.createQName(null, ".web_project.noderef"); diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java index c9f082c927..21fde3cb21 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxFactory.java @@ -24,24 +24,29 @@ */ package org.alfresco.wcm.sandbox; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; import org.alfresco.config.JNDIConstants; -import org.alfresco.model.WCMAppModel; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.mbeans.VirtServerRegistry; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.cmr.avm.locking.AVMLockingService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.DNSNameMangler; import org.alfresco.util.GUID; +import org.alfresco.util.ParameterCheck; import org.alfresco.wcm.util.WCMUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -57,9 +62,11 @@ public final class SandboxFactory extends WCMUtil private static Log logger = LogFactory.getLog(SandboxFactory.class); /** Services */ + private NodeService nodeService; private PermissionService permissionService; private AVMService avmService; - private NodeService nodeService; + private AVMLockingService avmLockingService; + private VirtServerRegistry virtServerRegistry; public void setNodeService(NodeService nodeService) { @@ -76,6 +83,17 @@ public final class SandboxFactory extends WCMUtil this.avmService = avmService; } + public void setAvmLockingService(AVMLockingService avmLockingService) + { + this.avmLockingService = avmLockingService; + } + + public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) + { + this.virtServerRegistry = virtServerRegistry; + } + + /** * Private constructor */ @@ -109,7 +127,7 @@ public final class SandboxFactory extends WCMUtil if (logger.isDebugEnabled()) { - logger.debug("Created staging sandbox store: " + stagingStoreName); + logger.debug("Created staging sandbox: " + stagingStoreName); } // we can either branch from an existing staging store or create a new structure @@ -200,27 +218,100 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, previewStoreName); } - return new SandboxInfo( new String[] { stagingStoreName, previewStoreName } ); + return getSandbox(stagingStoreName); + } + + /** + * Get sandbox info for given sandbox store id + * + * @param sandboxId + * @return SandboxInfo returns sandbox info or null if sandbox does not exist or is not visible + */ + public SandboxInfo getSandbox(String sandboxId) + { + AVMStoreDescriptor storeDesc = avmService.getStore(sandboxId); + if (storeDesc == null) + { + return null; + } + + String wpStoreId = WCMUtil.getWebProjectStoreId(sandboxId); + + String[] storeNames = null; + + // Check sandbox type + Map props = avmService.getStoreProperties(sandboxId); + QName sandboxType = null; + + // derive name for now + String name = null; + + if (props.containsKey(SandboxConstants.PROP_SANDBOX_STAGING_MAIN)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_STAGING_MAIN; + name = sandboxId; + storeNames = new String[] {sandboxId, WCMUtil.getCorrespondingPreviewStoreName(sandboxId)}; + } + else if ( props.containsKey( SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_STAGING_PREVIEW; + storeNames = new String[] {WCMUtil.getCorrespondingMainStoreName(sandboxId), sandboxId}; + } + else if (props.containsKey(SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN; + name = WCMUtil.getUserName(sandboxId); + storeNames = new String[] {sandboxId, WCMUtil.getCorrespondingPreviewStoreName(sandboxId)}; + } + else if (props.containsKey(SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_AUTHOR_PREVIEW; + name = WCMUtil.getUserName(sandboxId); + storeNames = new String[] {WCMUtil.getCorrespondingMainStoreName(sandboxId), sandboxId}; + } + else if (props.containsKey(SandboxConstants.PROP_SANDBOX_WORKFLOW_MAIN)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_WORKFLOW_MAIN; + name = WCMUtil.getWorkflowId(sandboxId); + storeNames = new String[] {sandboxId, WCMUtil.getCorrespondingPreviewStoreName(sandboxId)}; + } + else if (props.containsKey(SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW)) + { + sandboxType = SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW; + name = WCMUtil.getWorkflowId(sandboxId); + storeNames = new String[] {WCMUtil.getCorrespondingMainStoreName(sandboxId), sandboxId}; + } + + if ((storeNames == null) || (storeNames.length == 0)) + { + throw new AlfrescoRuntimeException("Must have at least one store"); + } + + if ((storeNames.length == 1) && (! sandboxType.equals(SandboxConstants.PROP_SANDBOX_STAGING_MAIN))) + { + throw new AlfrescoRuntimeException("Main store must be of type: " + SandboxConstants.PROP_SANDBOX_STAGING_MAIN); + } + + return new SandboxInfoImpl(wpStoreId, sandboxId, sandboxType, name, storeNames, new Date(storeDesc.getCreateDate()), storeDesc.getCreator()); } - protected void setStagingPermissions(String storeId, NodeRef webProjectNodeRef) + protected void setStagingPermissions(String storeId, NodeRef wpNodeRef) { - String storeName = WCMUtil.buildStagingStoreName(storeId); - NodeRef dirRef = AVMNodeConverter.ToNodeRef(-1, WCMUtil.buildStoreRootPath(storeName)); - - // Apply sepcific user permissions as set on the web project - // All these will be masked out - List userInfoRefs = nodeService.getChildAssocs( - webProjectNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + String storeName = WCMUtil.buildStagingStoreName(storeId); + NodeRef dirRef = AVMNodeConverter.ToNodeRef(-1, WCMUtil.buildStoreRootPath(storeName)); - for (ChildAssociationRef ref : userInfoRefs) - { - NodeRef userInfoRef = ref.getChildRef(); - String username = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); - String userrole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); + // Apply specific user permissions as set on the web project + // All these will be masked out - permissionService.setPermission(dirRef, username, userrole, true); - } + Map userRoles = WCMUtil.listWebUsers(nodeService, wpNodeRef); + + for (Map.Entry userRole : userRoles.entrySet()) + { + String username = userRole.getKey(); + String userrole = userRole.getValue(); + + permissionService.setPermission(dirRef, username, userrole, true); + } } public void setStagingPermissionMasks(String storeId) @@ -238,7 +329,7 @@ public final class SandboxFactory extends WCMUtil permissionService.setPermission(dirRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ, true); } - public void updateStagingAreaManagers(String storeId, NodeRef webProjectNodeRef, final List managers) + private void updateStagingAreaManagers(String storeId, NodeRef webProjectNodeRef, final List managers) { // The stores have the mask set in updateSandboxManagers String storeName = WCMUtil.buildStagingStoreName(storeId); @@ -256,10 +347,10 @@ public final class SandboxFactory extends WCMUtil } } - public void addStagingAreaUser(String storeId, String authority, String role) + public void addStagingAreaUser(String wpStoreId, String authority, String role) { // The stores have the mask set in updateSandboxManagers - String storeName = WCMUtil.buildStagingStoreName(storeId); + String storeName = WCMUtil.buildStagingStoreName(wpStoreId); NodeRef dirRef = AVMNodeConverter.ToNodeRef(-1, WCMUtil.buildStoreRootPath(storeName)); permissionService.setPermission(dirRef, authority, role, true); @@ -293,20 +384,23 @@ public final class SandboxFactory extends WCMUtil String userStoreName = WCMUtil.buildUserMainStoreName(storeId, username); String previewStoreName = WCMUtil.buildUserPreviewStoreName(storeId, username); - if (avmService.getStore(userStoreName) != null) + SandboxInfo userSandboxInfo = getSandbox(userStoreName); + if (userSandboxInfo != null) { - if (logger.isDebugEnabled()) - { - logger.debug("Not creating as store already exists: " + userStoreName); - } - return new SandboxInfo( new String[] { userStoreName, previewStoreName } ); + if (logger.isDebugEnabled()) + { + logger.debug("Not creating author sandbox as it already exists: " + userStoreName); + } + return userSandboxInfo; } avmService.createStore(userStoreName); String stagingStoreName = WCMUtil.buildStagingStoreName(storeId); + if (logger.isDebugEnabled()) - logger.debug("Created user sandbox store: " + userStoreName + - " above staging store " + stagingStoreName); + { + logger.debug("Created user sandbox: " + userStoreName + " above staging store " + stagingStoreName); + } // create a layered directory pointing to 'www' in the staging area avmService.createLayeredDirectory(WCMUtil.buildStoreRootPath(stagingStoreName), @@ -417,7 +511,8 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, userStoreName); dumpStoreProperties(avmService, previewStoreName); } - return new SandboxInfo( new String[] { userStoreName, previewStoreName } ); + + return getSandbox(userStoreName); } /** @@ -533,7 +628,232 @@ public final class SandboxFactory extends WCMUtil dumpStoreProperties(avmService, mainStoreName); dumpStoreProperties(avmService, previewStoreName); } - return new SandboxInfo( new String[] { mainStoreName, previewStoreName } ); + + return getSandbox(mainStoreName); + } + + /** + * Creates a workflow sandbox for the given user store. This will create a + * workflow sandbox layered over the user's main store. + * + * @param stagingStore The name of the staging store the user sandbox is layered over + * @param userStore The name of the user store to create the workflow for + * @return The store name of the main store in the workflow sandbox + */ + // TODO refactor AVMExpiredContentProcessor ... + public String createUserWorkflowSandbox(String stagingStore, String userStore) + { + // create the workflow 'main' store + String packageName = "workflow-" + GUID.generate(); + String workflowStoreName = userStore + STORE_SEPARATOR + packageName; + + this.avmService.createStore(workflowStoreName); + + if (logger.isDebugEnabled()) + logger.debug("Created user workflow sandbox store: " + workflowStoreName); + + // create a layered directory pointing to 'www' in the users store + this.avmService.createLayeredDirectory( + userStore + ":/" + JNDIConstants.DIR_DEFAULT_WWW, + workflowStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); + + // tag the store with the store type + this.avmService.setStoreProperty(workflowStoreName, + SandboxConstants.PROP_SANDBOX_AUTHOR_WORKFLOW_MAIN, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the name of the author's store this one is layered over + this.avmService.setStoreProperty(workflowStoreName, + SandboxConstants.PROP_AUTHOR_NAME, + new PropertyValue(DataTypeDefinition.TEXT, userStore)); + + // tag the store, oddly enough, with its own store name for querying. + this.avmService.setStoreProperty(workflowStoreName, + QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + workflowStoreName), + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the DNS name property + String path = workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; + // DNS name mangle the property name - can only contain value DNS characters! + String dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(stagingStore, packageName); + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, dnsProp), + new PropertyValue(DataTypeDefinition.TEXT, path)); + + // the main workflow store depends on the main user store (dist=1) + String prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 1)); + + // The main workflow store depends on the main staging store (dist=2) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; + this.avmService.setStoreProperty(workflowStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 2)); + + // snapshot the store + this.avmService.createSnapshot(workflowStoreName, null, null); + + // create the workflow 'preview' store + String previewStoreName = workflowStoreName + STORE_SEPARATOR + "preview"; + this.avmService.createStore(previewStoreName); + + if (logger.isDebugEnabled()) + logger.debug("Created user workflow sandbox preview store: " + previewStoreName); + + // create a layered directory pointing to 'www' in the workflow 'main' store + this.avmService.createLayeredDirectory( + workflowStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW, + previewStoreName + ":/", JNDIConstants.DIR_DEFAULT_WWW); + + // tag the store with the store type + this.avmService.setStoreProperty(previewStoreName, SandboxConstants.PROP_SANDBOX_WORKFLOW_PREVIEW, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with its own store name for querying. + avmService.setStoreProperty(previewStoreName, + QName.createQName(null, SandboxConstants.PROP_SANDBOX_STORE_PREFIX + previewStoreName), + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // tag the store with the DNS name property + path = previewStoreName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + + "/" + JNDIConstants.DIR_DEFAULT_APPBASE; + // DNS name mangle the property name - can only contain value DNS characters! + dnsProp = SandboxConstants.PROP_DNS + DNSNameMangler.MakeDNSName(userStore, packageName, "preview"); + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, dnsProp), + new PropertyValue(DataTypeDefinition.TEXT, path)); + + // The preview worfkflow store depends on the main workflow store (dist=1) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + workflowStoreName; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 1)); + + // The preview workflow store depends on the main user store (dist=2) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + userStore; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 2)); + + // The preview workflow store depends on the main staging store (dist=3) + prop_key = SandboxConstants.PROP_BACKGROUND_LAYER + stagingStore; + this.avmService.setStoreProperty(previewStoreName, QName.createQName(null, prop_key), + new PropertyValue(DataTypeDefinition.INT, 3)); + + // snapshot the store + this.avmService.createSnapshot(previewStoreName, null, null); + + // tag all related stores to indicate that they are part of a single sandbox + QName sandboxIdProp = QName.createQName(SandboxConstants.PROP_SANDBOXID + GUID.generate()); + this.avmService.setStoreProperty(workflowStoreName, sandboxIdProp, + new PropertyValue(DataTypeDefinition.TEXT, null)); + this.avmService.setStoreProperty(previewStoreName, sandboxIdProp, + new PropertyValue(DataTypeDefinition.TEXT, null)); + + // return the main workflow store name + return workflowStoreName; + } + + public List listSandboxes(final String wpStoreId, String userName) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + ParameterCheck.mandatoryString("userName", userName); + + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { + public List doWork() throws Exception + { + List stores = avmService.getStores(); + + List sbInfos = new ArrayList(); + for (AVMStoreDescriptor store : stores) + { + String storeName = store.getName(); + + // list main stores - not preview stores or workflow stores + if ((storeName.startsWith(wpStoreId)) && + (! WCMUtil.isPreviewStore(storeName)) && + (! WCMUtil.isWorkflowStore(storeName))) + { + sbInfos.add(getSandbox(storeName)); + } + } + + return sbInfos; + } + }, userName); + } + + public void deleteSandbox(String sbStoreId) + { + SandboxInfo sbInfo = getSandbox(sbStoreId); + + if (sbInfo != null) + { + String mainSandboxStore = sbInfo.getMainStoreName(); + + // found the sandbox to remove - remove the main store (eg. user main store, staging main store, workflow main store) + String path = WCMUtil.buildSandboxRootPath(mainSandboxStore); + + // Notify virtualisation server about removing this sandbox. + // + // Implementation note: + // + // Because the removal of virtual webapps in the + // virtualization server is recursive, it only + // needs to be given the name of the main store. + // + // This notification must occur *prior* to purging content + // within the AVM because the virtualization server must list + // the avm_webapps dir in each store to discover which + // virtual webapps must be unloaded. The virtualization + // server traverses the sandbox's stores in most-to-least + // dependent order, so clients don't have to worry about + // accessing a preview layer whose main layer has been torn + // out from under it. + + WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); + + // TODO: Use the .sandbox-id. property to delete all sandboxes, + // rather than assume a sandbox always had a single preview + // layer attached. + + // purge stores, eg. main store followed by preview store + String[] avmStoreNames = sbInfo.getStoreNames(); + + for (String avmStoreName : avmStoreNames) + { + // check it exists before we try to remove it + if (avmService.getStore(avmStoreName) != null) + { + // purge store from the system + avmService.purgeStore(avmStoreName); + } + + // remove any locks this user may have + avmLockingService.removeStoreLocks(avmStoreName); + } + } + } + + public void updateSandboxManagers(final String wpStoreId, NodeRef wpNodeRef, List managers) + { + // walk existing user sandboxes and reapply manager permissions to include new manager user + List sbInfos = AuthenticationUtil.runAs(new RunAsWork>() + { + public List doWork() throws Exception + { + return listSandboxes(wpStoreId, AuthenticationUtil.getSystemUserName()); + } + }, AuthenticationUtil.getSystemUserName()); + + for (SandboxInfo sbInfo : sbInfos) + { + if (sbInfo.getSandboxType().equals(SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)) + { + String username = sbInfo.getName(); + updateUserSandboxManagers(wpStoreId, managers, username); + } + } + + updateStagingAreaManagers(wpStoreId, wpNodeRef, managers); } /** @@ -545,7 +865,7 @@ public final class SandboxFactory extends WCMUtil * @param managers The list of authorities who have ContentManager role in the web project * @param username Username of the user sandbox to update */ - public void updateSandboxManagers(final String storeId, final List managers, final String username) + private void updateUserSandboxManagers(final String storeId, final List managers, final String username) { final String userStoreName = WCMUtil.buildUserMainStoreName(storeId, username); final String previewStoreName = WCMUtil.buildUserPreviewStoreName(storeId, username); diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxInfo.java b/source/java/org/alfresco/wcm/sandbox/SandboxInfo.java index 80202b7b7c..d7c6c6bc79 100644 --- a/source/java/org/alfresco/wcm/sandbox/SandboxInfo.java +++ b/source/java/org/alfresco/wcm/sandbox/SandboxInfo.java @@ -24,23 +24,43 @@ */ package org.alfresco.wcm.sandbox; -import java.io.Serializable; +import java.util.Date; + +import org.alfresco.service.namespace.QName; /** * Provides information about a WCM sandbox created by SandboxFactory. */ -public final class SandboxInfo implements Serializable +public interface SandboxInfo { - private static final long serialVersionUID = 3615436375385857404L; - - String [] store_names_; - public SandboxInfo(String [] store_names) - { - store_names_ = store_names; - } - /** - * A list of names of the stores within this sandbox. + * Get the name + * + * @return String name + */ + public String getName(); + + /** + * The sandbox store id + */ + public String getSandboxId(); + + /** + * The web project store id + */ + public String getWebProjectId(); + + /** + * The sandbox type ... for now a QName, based on existing SandboxConstants + */ + public QName getSandboxType(); + + public Date getCreatedDate(); + + public String getCreator(); + + /** + * A list of ids of the stores within this sandbox. * The "main" store should come first in this list; * any other stores should appear in the order that * they are overlaid on "main" (e.g.: any "preview" @@ -48,10 +68,10 @@ public final class SandboxInfo implements Serializable *

* Note: all sandboxes must have a "main" layer. */ - public String [] getStoreNames() { return store_names_; } + public String[] getStoreNames(); /** - * The name of the "main" store within this sandbox. + * The id of the "main" store within this sandbox. */ - public String getMainStoreName() { return store_names_[0]; } + public String getMainStoreName(); } diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxInfoImpl.java b/source/java/org/alfresco/wcm/sandbox/SandboxInfoImpl.java new file mode 100644 index 0000000000..4640f6e50a --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/SandboxInfoImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox; + +import java.util.Date; + +import org.alfresco.service.namespace.QName; + +/** + * Provides information about a WCM sandbox created by Sandbox Service/Factory + */ +public class SandboxInfoImpl implements SandboxInfo +{ + private String wpStoreId; + private String sbStoreId; + private QName sandboxType; + private String name; + private String[] storeNames; + private Date createdDate; + private String creator; + + /* package */ SandboxInfoImpl(String wpStoreId, String sbStoreId, QName sandboxType, String name, String[] storeNames, Date createdDate, String creator) + { + this.wpStoreId = wpStoreId; + this.sbStoreId = sbStoreId; + this.sandboxType = sandboxType; + this.name = name; + this.storeNames = storeNames; + this.createdDate = createdDate; + this.creator = creator; + } + + // note: currently derived - for author sandbox this is the username, for staging sandbox this is the sandbox id + public String getName() + { + return this.name; + } + + public String getWebProjectId() + { + return this.wpStoreId; + } + + public String getSandboxId() + { + return this.sbStoreId; + } + + public QName getSandboxType() + { + return this.sandboxType; + } + + public Date getCreatedDate() + { + return this.createdDate; + } + + public String getCreator() + { + return this.creator; + } + + /** + * A list of names of the stores within this sandbox. + * The "main" store should come first in this list; + * any other stores should appear in the order that + * they are overlaid on "main" (e.g.: any "preview" + * layers should come afterward, in "lowest first" order). + *

+ * Note: all sandboxes must have a "main" layer. + */ + public String [] getStoreNames() { return storeNames; } + + /** + * The name of the "main" store within this sandbox. + */ + public String getMainStoreName() + { + return storeNames[0]; + } +} diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxService.java b/source/java/org/alfresco/wcm/sandbox/SandboxService.java new file mode 100644 index 0000000000..17142d94fc --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/SandboxService.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.namespace.QName; + + +/** + * Sandbox Service fundamental API. + *

+ * This service API is designed to support the public facing Sandbox APIs. + * + * @author janv + */ +public interface SandboxService +{ + /** + * Create author/user sandbox within a web project for the current user + *

+ * If the author sandbox already exists for this web project then it will be returned + * + * @param wpStoreId web project store id + * @return SandboxInfo the created user sandbox info + */ + public SandboxInfo createAuthorSandbox(String wpStoreId); + + /** + * Create author/user sandbox within a web project for the given user + *

+ * If the author sandbox already exists for this web project then it will be returned + *

+ * Current user must be a content manager for the web project + * + * @param wpStoreId web project store id + * @param userName user name + * @return SandboxInfo the created user sandbox info + */ + public SandboxInfo createAuthorSandbox(String wpStoreId, String userName); + + /** + * List the available sandboxes for the current user and given web project + * + * @param wpStoreId web project store id + * @return List list of sandbox info + */ + public List listSandboxes(String wpStoreId); + + /** + * List the available sandboxes for the given user and web project + *

+ * Current user must be a content manager for the web project + * + * @param wpStoreId web project store id + * @param userName user name + * @return List list of sandbox info + */ + public List listSandboxes(String wpStoreId, String userName); + + /** + * Return true if sandbox is visible to user and is of given type + *

+ * eg. isSandboxType("test123--myusername", SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN) + * + * @param sbStoreId sandbox store id + * @param sandboxType sandbox type (see SandboxConstants) + * @return boolean true, if sandbox exists with given type + */ + public boolean isSandboxType(String sbStoreId, QName sandboxType); + + /** + * Get sandbox info + * + * @param sbStoreId sandbox store id + * @return SandboxInfo null if sandbox does not exist or is not visible to the current user + */ + public SandboxInfo getSandbox(String sbStoreId); + + /** + * Gets author/user sandbox info for the current user + *

+ * Returns null if the author sandbox can not be found + * + * @param wpStoreId web project store id + * @return SandboxInfo author sandbox info + */ + public SandboxInfo getAuthorSandbox(String wpStoreId); + + /** + * Gets author/user sandbox info for the given user + *

+ * Returns null if the user sandbox can not be found + *

+ * Current user must be a content manager for the web project + * + * @param wpStoreId web project store id + * @param userName userName + * @return SandboxInfo author sandbox info + */ + public SandboxInfo getAuthorSandbox(String wpStoreId, String userName); + + /** + * Gets staging sandbox info + *

+ * Returns null if the staging sandbox can not be found + * + * @param wpStoreId web project store id + * @return SandboxInfo staging sandbox info + */ + public SandboxInfo getStagingSandbox(String wpStoreId); + + /** + * Delete the sandbox + *

+ * If the sandbox does not exist, will log a warning and succeed + *

+ * Current user must be a content manager for the web project (associated with the sandbox) + * + * @param sbStoreId sandbox store id + */ + public void deleteSandbox(String sbStoreId); + + /** + * List changed items for given sandbox (eg. for user sandbox compared to staging sandbox) + *

+ * Note: This will list new/modified/deleted items from the root directory and below, including all web apps + * + * @param sbStoreId sandbox store id + * @param includeDeleted if true, include deleted items as well as new/modified items + * @return List list of changed items + */ + public List listChangedItems(String sbStoreId, boolean includeDeleted); + + /** + * List changed items for given sandbox and web app (eg. in user sandbox) + *

+ * Note: This will list new/modified/deleted items for the given web app + * + * @param sbStoreId sandbox store id + * @param webApp web app to filter by + * @param includeDeleted if true, include deleted items as well as new/modified items + * @return List list of changed items + */ + public List listChangedItemsWebApp(String sbStoreId, String webApp, boolean includeDeleted); + + /** + * List changed items for given sandbox path (eg. between user sandbox and staging sandbox) + *

+ * Note: This will list new/modified/deleted items from the directory and below. The destination path will be dervied. + * + * @param avmSrcPath source sandbox path (an AVM path) + * @param includeDeleted if true, include deleted items as well as new/modified items + * @return List list of changed items + */ + public List listChangedItemsDir(String avmSrcPath, boolean includeDeleted); + + /** + * List changed (new/modified/deleted) items between any two sandbox paths + * + * @param avmSrcPath source sandbox path (an AVM path) + * @param avmDstPath destination sandbox path (an AVM path) + * @param includeDeleted if true, include deleted items as well as new/modified items + * @return List list of changed items + */ + public List listChangedItems(String avmSrcPath, String avmDstPath, boolean includeDeleted); + + /** + * Submit all changed items for given sandbox (eg. from user sandbox to staging sandbox) + *

+ * Note: This will submit new/modified/deleted items from the root directory and below, including all web apps + *

+ * @param sbStoreId sandbox store id + */ + public void submitAll(String sbStoreId, String submitLabel, String submitComment); + + /** + * Submit all changed items for given sandbox and web app (eg. in user sandbox) + *

+ * Note: This will submit new/modified/deleted items for the given web app + * + * @param sbStoreId sandbox store id + * @param webApp web app to filter by + */ + public void submitAllWebApp(String sbStoreId, String webApp, String submitLabel, String submitComment); + + /** + * Submit all changed items for given sandbox path (eg. in user sandbox) + *

+ * Note: This will submit new/modified/deleted items from the directory and below + * + * @param avmDirectoryPath path to filter by + */ + public void submitAllDir(String avmDirectoryPath, String submitLabel, String submitComment); + + /** + * Submit list of changed items for given sandbox (eg. from user sandbox to staging sandbox) + * + * @param sbStoreId sandbox store id + * @param items list of AVM node descriptors + */ + public void submitList(String sbStoreId, List items, String submitLabel, String submitComment); + + /** + * Submit list of changed items for given sandbox (eg. from user sandbox to staging sandbox) + * + * @param sbStoreId sandbox store id + * @param items list of AVM node descriptors + * @param expirationDates map of for those items set with an expiration date, or can be null (if no expiration dates) + */ + public void submitList(String sbStoreId, List items, Map expirationDates, final String submitLabel, final String submitComment); + + /** + * Revert all changed items for given sandbox (eg. in user sandbox) + *

+ * Note: This will revert new/modified/deleted items from the root directory and below, including all web apps + * + * @param sbStoreId sandbox store id + */ + public void revertAll(String sbStoreId); + + /** + * Revert all changed items for given sandbox and web app (eg. in user sandbox) + *

+ * Note: This will revert new/modified/deleted items for the given web app + * + * @param sbStoreId sandbox store id + * @param webApp web app to filter by + */ + public void revertAllWebApp(String sbStoreId, String webApp); + + /** + * Revert all changed items for given sandbox path (eg. in user sandbox) + *

+ * Note: This will revert new/modified/deleted items from the directory and below + * + * @param avmDirectoryPath path to filter by + */ + public void revertAllDir(String avmDirectoryPath); + + /** + * Revert list of changed items for given sandbox (eg. in user sandbox) + * + * @param items list of AVM node descriptors + */ + public void revertList(String sbStoreId, List items); + + /** + * Revert sandbox to a specific snapshot version ID (ie. for staging sandbox) + *

+ * Current user must be a content manager for the web project + * + * @param sbStoreId staging sandbox store id + * @param version version + */ + public void revertSnapshot(String sbStoreId, int version); + + /** + * List all snapshots (sandbox versions) for the given sandbox (ie. for staging sandbox) + *

+ * Current user must be a content manager for the web project + * + * @param sbStoreId staging sandbox store id + * @param includeSystemGenerated if false will ignore system generated snapshots else true to get all snapshots + * @return List list of AVM version descriptors + */ + public List listSnapshots(String sbStoreId, boolean includeSystemGenerated); + + /** + * List snapshots (sandbox versions) for the given sandbox between given dates (ie. for staging sandbox) + *

+ * Current user must be a content manager for the web project + * + * @param sbStoreId staging sandbox store id + * @param from from date + * @param to to date + * @param includeSystemGenerated if false will ignore system generated snapshots else true to get all snapshots + * @return List list of AVM version descriptors + */ + public List listSnapshots(String sbStoreId, Date from, Date to, boolean includeSystemGenerated); + +} diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java new file mode 100644 index 0000000000..2a5f5b4660 --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImpl.java @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMDAOs; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.avm.actions.AVMRevertStoreAction; +import org.alfresco.repo.avm.actions.AVMUndoSandboxListAction; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.cmr.avm.locking.AVMLockingService; +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.service.cmr.avmsync.AVMSyncService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.NameMatcher; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.VirtServerUtils; +import org.alfresco.wcm.util.WCMUtil; +import org.alfresco.wcm.util.WCMWorkflowUtil; +import org.alfresco.wcm.webproject.WebProjectInfo; +import org.alfresco.wcm.webproject.WebProjectService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Sandbox Service fundamental API. + *

+ * This service API is designed to support the public facing Sandbox APIs. + * + * @author janv + */ +public class SandboxServiceImpl extends WCMUtil implements SandboxService +{ + /** Logger */ + private static Log logger = LogFactory.getLog(SandboxServiceImpl.class); + + private WebProjectService wpService; + private SandboxFactory sandboxFactory; + private AVMService avmService; + private AVMLockingService avmLockingService; + private AVMSyncService avmSyncService; + private NameMatcher nameMatcher; + private VirtServerRegistry virtServerRegistry; + private ActionService actionService; + private WorkflowService workflowService; + + public void setWebProjectService(WebProjectService wpService) + { + this.wpService = wpService; + } + + public void setSandboxFactory(SandboxFactory sandboxFactory) + { + this.sandboxFactory = sandboxFactory; + } + + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + public void setAvmLockingService(AVMLockingService avmLockingService) + { + this.avmLockingService = avmLockingService; + } + + public void setAvmSyncService(AVMSyncService avmSyncService) + { + this.avmSyncService = avmSyncService; + } + + public void setNameMatcher(NameMatcher nameMatcher) + { + this.nameMatcher = nameMatcher; + } + + public void setVirtServerRegistry(VirtServerRegistry virtServerRegistry) + { + this.virtServerRegistry = virtServerRegistry; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#createAuthorSandbox(java.lang.String) + */ + public SandboxInfo createAuthorSandbox(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + String currentUserName = AuthenticationUtil.getCurrentEffectiveUserName(); + SandboxInfo sbInfo = null; + + if (! wpService.isWebUser(wpStoreId, currentUserName)) + { + throw new AccessDeniedException("Only web project users may create their own (author) sandbox for '"+currentUserName+"' (store id: "+wpStoreId+")"); + } + else + { + sbInfo = createAuthorSandboxImpl(wpStoreId, currentUserName); + } + + return sbInfo; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#createAuthorSandbox(java.lang.String, java.lang.String) + */ + public SandboxInfo createAuthorSandbox(String wpStoreId, String userName) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + ParameterCheck.mandatoryString("userName", userName); + + // is the current user a content manager for this web project ? + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may create author sandbox for '"+userName+"' (store id: "+wpStoreId+")"); + } + + return createAuthorSandboxImpl(wpStoreId, userName); + } + + private SandboxInfo createAuthorSandboxImpl(String wpStoreId, String userName) + { + WebProjectInfo wpInfo = wpService.getWebProject(wpStoreId); + + final NodeRef wpNodeRef = wpInfo.getNodeRef(); + final List managers = new ArrayList(4); + + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + // retrieve the list of managers from the existing users + Map existingUserRoles = wpService.listWebUsers(wpNodeRef); + for (Map.Entry userRole : existingUserRoles.entrySet()) + { + String username = userRole.getKey(); + String userrole = userRole.getValue(); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) + { + managers.add(username); + } + } + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + String role = wpService.getWebUserRole(wpNodeRef, userName); + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userName, role); + + List sandboxInfoList = new LinkedList(); + sandboxInfoList.add(sbInfo); + + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, wpService.listWebApps(wpNodeRef)); + AlfrescoTransactionSupport.bindListener(tl); + + if (logger.isInfoEnabled()) + { + logger.info("Created author sandbox: " + sbInfo.getSandboxId() + " (web project id: " + wpStoreId + ")"); + } + + return sbInfo; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listSandboxes(java.lang.String) + */ + public List listSandboxes(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + return sandboxFactory.listSandboxes(wpStoreId, AuthenticationUtil.getCurrentEffectiveUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listSandboxes(java.lang.String, java.lang.String) + */ + public List listSandboxes(final String wpStoreId, String userName) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + ParameterCheck.mandatoryString("userName", userName); + + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may list sandboxes for '"+userName+"' (web project id: "+wpStoreId+")"); + } + + return sandboxFactory.listSandboxes(wpStoreId, userName); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#isSandboxType(java.lang.String, org.alfresco.service.namespace.QName) + */ + public boolean isSandboxType(String sbStoreId, QName sandboxType) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + ParameterCheck.mandatory("sandboxType", sandboxType); + + SandboxInfo sbInfo = sandboxFactory.getSandbox(sbStoreId); + if (sbInfo != null) + { + return sbInfo.getSandboxType().equals(sandboxType); + } + return false; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#getSandbox(java.lang.String) + */ + public SandboxInfo getSandbox(String sbStoreId) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + return sandboxFactory.getSandbox(sbStoreId); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#getAuthorSandbox(java.lang.String) + */ + public SandboxInfo getAuthorSandbox(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + String currentUserName = AuthenticationUtil.getCurrentEffectiveUserName(); + return getSandbox(WCMUtil.buildUserMainStoreName(WCMUtil.buildStagingStoreName(wpStoreId), currentUserName)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#getUserSandbox(java.lang.String, java.lang.String) + */ + public SandboxInfo getAuthorSandbox(String wpStoreId, String userName) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + ParameterCheck.mandatoryString("userName", userName); + + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may get author sandbox for '"+userName+"' (web project id: "+wpStoreId+")"); + } + + return getSandbox(WCMUtil.buildUserMainStoreName(WCMUtil.buildStagingStoreName(wpStoreId), userName)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#getStagingSandbox(java.lang.String) + */ + public SandboxInfo getStagingSandbox(String wpStoreId) + { + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + + return getSandbox(WCMUtil.buildStagingStoreName(wpStoreId)); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#deleteSandbox(java.lang.String) + */ + public void deleteSandbox(String sbStoreId) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + + String currentUserName = AuthenticationUtil.getCurrentEffectiveUserName(); + if (sbStoreId.equals(WCMUtil.buildUserMainStoreName(wpStoreId, currentUserName))) + { + // author may delete their own sandbox + sandboxFactory.deleteSandbox(sbStoreId); + } + else + { + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may delete sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); + } + + if (sbStoreId.equals(wpStoreId)) + { + throw new AlfrescoRuntimeException("Cannot delete staging sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); + } + + // content manager may delete sandboxes, except staging sandbox + sandboxFactory.deleteSandbox(sbStoreId); + } + + if (logger.isInfoEnabled()) + { + logger.info("Deleted sandbox: " + sbStoreId + " (web project id: " + wpStoreId + ")"); + } + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listChangedItems(java.lang.String, boolean) + */ + public List listChangedItems(String sbStoreId, boolean includeDeleted) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + // no filtering + String avmDirectoryPath = sbStoreId+":/"; + return listChangedItemsDir(avmDirectoryPath, includeDeleted); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listChangedItemsWebApp(java.lang.String, java.lang.String, boolean) + */ + public List listChangedItemsWebApp(String sbStoreId, String webApp, boolean includeDeleted) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + ParameterCheck.mandatoryString("webApp", webApp); + + // filter by current webapp + String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); + return listChangedItemsDir(avmDirectoryPath, includeDeleted); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listChangedItemsDir(java.lang.String, boolean) + */ + public List listChangedItemsDir(String avmDirectoryPath, boolean includeDeleted) + { + ParameterCheck.mandatoryString("avmDirectoryPath", avmDirectoryPath); + + String sandboxId = WCMUtil.getSandboxStoreId(avmDirectoryPath); + + // TODO - allow list for any sandbox + if (! WCMUtil.isUserStore(sandboxId)) + { + throw new AlfrescoRuntimeException("Not an author sandbox: "+sandboxId); + } + + // build the paths to the stores to compare - filter by given directory path + String wpStoreId = WCMUtil.getWebProjectStoreId(sandboxId); + String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); + + String relativePath = WCMUtil.getStoreRelativePath(avmDirectoryPath); + + String srcPath = sandboxId + AVM_STORE_SEPARATOR + relativePath; + String dstPath = stagingSandboxId + AVM_STORE_SEPARATOR + relativePath; + + return listChangedItems(srcPath, dstPath, includeDeleted); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listChangedItems(java.lang.String, java.lang.String, boolean) + */ + public List listChangedItems(String avmSrcPath, String avmDstPath, boolean includeDeleted) + { + return listChangedItems(-1, avmSrcPath, -1, avmDstPath, includeDeleted); + } + + private List listChangedItems(int srcVersion, String srcPath, int dstVersion, String dstPath, boolean includeDeleted) + { + ParameterCheck.mandatoryString("srcPath", srcPath); + ParameterCheck.mandatoryString("dstPath", dstPath); + + long start = System.currentTimeMillis(); + + List diffs = avmSyncService.compare(srcVersion, srcPath, dstVersion, dstPath, nameMatcher); + + List items = new ArrayList(diffs.size()); + + for (AVMDifference diff : diffs) + { + // convert each diff record into an AVM node descriptor + String sourcePath = diff.getSourcePath(); + AVMNodeDescriptor node = avmService.lookup(-1, sourcePath, includeDeleted); + if (node != null) + { + items.add(node); + } + } + + if (logger.isTraceEnabled()) + { + logger.trace("listModifiedItems: "+items.size()+" items in "+(System.currentTimeMillis()-start)+" ms (between "+srcVersion+","+srcPath+" and "+dstVersion+","+dstPath); + } + + return items; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitAll(java.lang.String, java.lang.String, java.lang.String) + */ + public void submitAll(String sbStoreId, String submitLabel, String submitComment) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String avmDirectoryPath = sbStoreId+":/"; + submitAllDir(avmDirectoryPath, submitLabel, submitComment); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitAllWebApp(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public void submitAllWebApp(String sbStoreId, String webApp, String submitLabel, String submitComment) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + ParameterCheck.mandatoryString("webApp", webApp); + + String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); + submitAllDir(avmDirectoryPath, submitLabel, submitComment); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitAllDir(java.lang.String, java.lang.String, java.lang.String) + */ + public void submitAllDir(String avmDirectoryPath, String submitLabel, String submitComment) + { + ParameterCheck.mandatoryString("avmDirectoryPath", avmDirectoryPath); + + String sbStoreId = WCMUtil.getSandboxStoreId(avmDirectoryPath); + + List items = listChangedItemsDir(avmDirectoryPath, true); + + submitList(sbStoreId, items, submitLabel, submitComment); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitList(java.lang.String, java.util.List, java.lang.String, java.lang.String) + */ + public void submitList(String sbStoreId, List items, final String submitLabel, final String submitComment) + { + submitList(sbStoreId, items, null, submitLabel, submitComment); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#submitList(java.lang.String, java.util.List, java.util.Map, java.lang.String, java.lang.String) + */ + public void submitList(String sbStoreId, List items, Map expirationDates, final String submitLabel, final String submitComment) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + // direct submit to the staging area (without workflow) + + // TODO - consider submit to higher-level sandbox, not just to staging + if (! WCMUtil.isUserStore(sbStoreId)) + { + throw new AlfrescoRuntimeException("Not an author sandbox: "+sbStoreId); + } + + // construct diffs for selected items for submission + String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + String stagingSandboxId = WCMUtil.buildStagingStoreName(wpStoreId); + + final List diffs = new ArrayList(items.size()); + + for (AVMNodeDescriptor item : items) + { + String relativePath = WCMUtil.getStoreRelativePath(item.getPath()); + + String srcPath = sbStoreId + AVM_STORE_SEPARATOR + relativePath; + String dstPath = stagingSandboxId + AVM_STORE_SEPARATOR + relativePath; + + AVMDifference diff = new AVMDifference(-1, srcPath, -1, dstPath, AVMDifference.NEWER); + diffs.add(diff); + + if (expirationDates != null) + { + // process the expiration date (if any) + processExpirationDate(srcPath, expirationDates); + } + + // recursively remove locks from this item + recursivelyRemoveLocks(wpStoreId, -1, avmService.lookup(-1, srcPath, true), srcPath); + + // check to see if destPath forces a notification of the virtualization server + // (e.g.: it might be a path to a jar file within WEB-INF/lib). + if (VirtServerUtils.requiresUpdateNotification(dstPath)) + { + // Bind the post-commit transaction listener with data required for virtualization server notification + UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(dstPath); + AlfrescoTransactionSupport.bindListener(tl); + } + } + + // write changes to layer so files are marked as modified + + // Submit is done as system as the staging store is read only + // We could add support to runIgnoringStoreACls + + // TODO review flatten - assumes webapps, hence currently flattens at /www/avm_webapps level + // also review flatten for SimpleAVMSubmitAction and AVMSubmitAction + final String sandboxPath = WCMUtil.buildSandboxRootPath(sbStoreId); + final String stagingPath = WCMUtil.buildSandboxRootPath(stagingSandboxId); + + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + avmSyncService.update(diffs, null, true, true, false, false, submitLabel, submitComment); + AVMDAOs.Instance().fAVMNodeDAO.flush(); + avmSyncService.flatten(sandboxPath, stagingPath); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#revertAll(java.lang.String, java.lang.String) + */ + public void revertAll(String sbStoreId) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String avmDirectoryPath = sbStoreId+":/"; + revertAllDir(avmDirectoryPath); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#revertAll(java.lang.String, java.lang.String) + */ + public void revertAllWebApp(String sbStoreId, String webApp) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + ParameterCheck.mandatoryString("webApp", webApp); + + String avmDirectoryPath = WCMUtil.buildStoreWebappPath(sbStoreId, webApp); + revertAllDir(avmDirectoryPath); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#revertAll(java.lang.String) + */ + public void revertAllDir(String avmDirectoryPath) + { + ParameterCheck.mandatoryString("avmDirectoryPath", avmDirectoryPath); + + String sbStoreId = WCMUtil.getSandboxStoreId(avmDirectoryPath); + + List items = listChangedItemsDir(avmDirectoryPath, true); + + revertList(sbStoreId, items); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#revertList(java.lang.String, java.lang.String, java.util.List) + */ + public void revertList(String sbStoreId, List items) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + List> versionPaths = new ArrayList>(items.size()); + + List tasks = null; + for (AVMNodeDescriptor node : items) + { + if (tasks == null) + { + tasks = WCMWorkflowUtil.getAssociatedTasksForSandbox(workflowService, WCMUtil.getSandboxStoreId(node.getPath())); + } + if (WCMWorkflowUtil.getAssociatedTasksForNode(avmService, node, tasks).size() == 0) + { + String revertPath = node.getPath(); + versionPaths.add(new Pair(-1, revertPath)); + + if (VirtServerUtils.requiresUpdateNotification(revertPath)) + { + // Bind the post-commit transaction listener with data required for virtualization server notification + UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(revertPath); + AlfrescoTransactionSupport.bindListener(tl); + } + } + } + + Map args = new HashMap(1, 1.0f); + args.put(AVMUndoSandboxListAction.PARAM_NODE_LIST, (Serializable)versionPaths); + Action action = actionService.createAction(AVMUndoSandboxListAction.NAME, args); + actionService.executeAction(action, null); // dummy action ref, list passed as action arg + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listSnapshots(java.lang.String, boolean) + */ + public List listSnapshots(String sbStoreId, boolean includeSystemGenerated) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); + } + + List allVersions = avmService.getStoreVersions(sbStoreId); + return listSnapshots(allVersions, includeSystemGenerated); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#listSnapshots(java.lang.String, java.util.Date, java.util.Date, boolean) + */ + public List listSnapshots(String sbStoreId, Date from, Date to, boolean includeSystemGenerated) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may list snapshots '"+sbStoreId+"' (web project id: "+wpStoreId+")"); + } + + List versionsToFilter = avmService.getStoreVersions(sbStoreId, from, to); + return listSnapshots(versionsToFilter, includeSystemGenerated); + } + + private List listSnapshots(List versionsToFilter, boolean includeSystemGenerated) + { + List versions = new ArrayList(versionsToFilter.size()); + + for (int i = versionsToFilter.size() - 1; i >= 0; i--) // reverse order + { + VersionDescriptor item = versionsToFilter.get(i); + + // only display snapshots with a valid tag - others are system generated snapshots + if ((includeSystemGenerated == true) || ((item.getTag() != null) && (item.getVersionID() != 0))) + { + versions.add(item); + } + } + + return versions; + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.sandbox.SandboxService#revertSnapshot(java.lang.String, int) + */ + public void revertSnapshot(final String sbStoreId, final int version) + { + ParameterCheck.mandatoryString("sbStoreId", sbStoreId); + + String wpStoreId = WCMUtil.getWebProjectStoreId(sbStoreId); + if (! wpService.isContentManager(wpStoreId)) + { + throw new AccessDeniedException("Only content managers may revert staging sandbox '"+sbStoreId+"' (web project id: "+wpStoreId+")"); + } + + // do this as system as the staging area has restricted access (and content manager may not have permission to delete children, for example) + List diffs = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { + public List doWork() throws Exception + { + String sandboxPath = WCMUtil.buildSandboxRootPath(sbStoreId); + + List diffs = avmSyncService.compare(-1, sandboxPath, version, sandboxPath, null); + + Map args = new HashMap(1, 1.0f); + args.put(AVMRevertStoreAction.PARAM_VERSION, version); + Action action = actionService.createAction(AVMRevertStoreAction.NAME, args); + actionService.executeAction(action, AVMNodeConverter.ToNodeRef(-1, sbStoreId + AVM_STORE_SEPARATOR + "/")); + return diffs; + } + }, AuthenticationUtil.getSystemUserName()); + + // See if any of the files being reverted require notification of the virt server, to update the webapp + for (AVMDifference diff : diffs) + { + if (VirtServerUtils.requiresUpdateNotification(diff.getSourcePath())) + { + // Bind the post-commit transaction listener with data required for virtualization server notification + UpdateSandboxTransactionListener tl = new UpdateSandboxTransactionListener(diff.getSourcePath()); + AlfrescoTransactionSupport.bindListener(tl); + break; + } + } + } + + /** + * Sets up the expiration date for the given source path + * + * @param srcPath The path to set the expiration date for + */ + private void processExpirationDate(String srcPath, Map expirationDates) + { + // if an expiration date has been set for this item we need to + // add the expires aspect and the date supplied + Date expirationDate = expirationDates.get(srcPath); + if (expirationDate == null) + { + return; + } + + // make sure the aspect is present + if (avmService.hasAspect(-1, srcPath, WCMAppModel.ASPECT_EXPIRES) == false) + { + avmService.addAspect(srcPath, WCMAppModel.ASPECT_EXPIRES); + } + + // set the expiration date + avmService.setNodeProperty(srcPath, WCMAppModel.PROP_EXPIRATIONDATE, + new PropertyValue(DataTypeDefinition.DATETIME, expirationDate)); + + if (logger.isDebugEnabled()) + { + logger.debug("Set expiration date of " + expirationDate + " for " + srcPath); + } + } + + /** + * Recursively remove locks from a path. Walking child folders looking for files + * to remove locks from. + */ + private void recursivelyRemoveLocks(String wpStoreId, int version, AVMNodeDescriptor desc, String absoluteAVMPath) + { + if (desc.isFile() || desc.isDeletedFile()) + { + avmLockingService.removeLock(wpStoreId, WCMUtil.getStoreRelativePath(absoluteAVMPath)); + } + else + { + if (desc.isDeletedDirectory()) + { + // lookup the previous child and get its contents + final List history = avmService.getHistory(desc, 2); + if (history.size() <= 1) + { + return; + } + desc = history.get(1); + } + + Map list = avmService.getDirectoryListingDirect(desc, true); + for (Map.Entry child : list.entrySet()) + { + String name = child.getKey(); + AVMNodeDescriptor childDesc = child.getValue(); + recursivelyRemoveLocks(wpStoreId, version, childDesc, absoluteAVMPath + "/" + name); + } + } + } + + /** + * Create Sandbox Transaction listener - invoked after commit + */ + private class CreateSandboxTransactionListener extends TransactionListenerAdapter + { + private List sandboxInfoList; + private List webAppNames; + + public CreateSandboxTransactionListener(List sandboxInfoList, List webAppNames) + { + this.sandboxInfoList = sandboxInfoList; + this.webAppNames = webAppNames; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() + */ + @Override + public void afterCommit() + { + // Handle notification to the virtualization server + // (this needs to occur after the sandboxes are created in the main txn) + + // reload virtualisation server for webapp(s) in this web project + for (SandboxInfo sandboxInfo : this.sandboxInfoList) + { + String newlyInvitedStoreName = WCMUtil.buildStagingStoreName(sandboxInfo.getMainStoreName()); + + for (String webAppName : webAppNames) + { + String path = WCMUtil.buildStoreWebappPath(newlyInvitedStoreName, webAppName); + WCMUtil.updateVServerWebapp(virtServerRegistry, path, true); + } + } + } + } + + /** + * Update Sandbox Transaction listener - invoked after submit or revert + */ + private class UpdateSandboxTransactionListener extends TransactionListenerAdapter + { + private String virtUpdatePath; + + public UpdateSandboxTransactionListener(String virtUpdatePath) + { + this.virtUpdatePath = virtUpdatePath; + } + + /** + * @see org.alfresco.repo.transaction.TransactionListenerAdapter#afterCommit() + */ + @Override + public void afterCommit() + { + // The virtualization server might need to be notified + // because one or more of the files submitted / reverted could alter + // the behavior the virtual webapp in the target of the submit. + // For example, the user might be submitting a new jar or web.xml file. + // + // This must take place after the transaction has been completed; + + // force an update of the virt server if necessary + if (this.virtUpdatePath != null) + { + WCMUtil.updateVServerWebapp(virtServerRegistry, this.virtUpdatePath, true); + } + } + } +} diff --git a/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java new file mode 100644 index 0000000000..47282b45d3 --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/SandboxServiceImplTest.java @@ -0,0 +1,1523 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.config.JNDIConstants; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +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.VersionDescriptor; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.alfresco.wcm.util.WCMUtil; +import org.alfresco.wcm.webproject.WebProjectInfo; +import org.alfresco.wcm.webproject.WebProjectService; +import org.springframework.context.ApplicationContext; + +/** + * Sandbox Service implementation unit test + * + * @author janv + */ +public class SandboxServiceImplTest extends TestCase +{ + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + // + // test data + // + + private static final String TEST_RUN = ""+System.currentTimeMillis(); + private static final boolean CLEAN = true; // cleanup during teardown + + // base web project + private static final String TEST_WEBPROJ_DNS = "testSandbox-"+TEST_RUN; + private static final String TEST_WEBPROJ_NAME = "testSandbox Web Project Display Name - "+TEST_RUN; + private static final String TEST_WEBPROJ_TITLE = "This is my title"; + private static final String TEST_WEBPROJ_DESCRIPTION = "This is my description"; + private static final String TEST_WEBPROJ_DEFAULT_WEBAPP = WCMUtil.DIR_ROOT; + //private static final boolean TEST_WEBPROJ_USE_AS_TEMPLATE = true; + private static final boolean TEST_WEBPROJ_DONT_USE_AS_TEMPLATE = false; + + // base sandbox + private static final String TEST_SANDBOX = TEST_WEBPROJ_DNS; + + + private static final String USER_ADMIN = "admin"; + + private static final String TEST_USER = "testSandboxUser-"+TEST_RUN; + + private static final String USER_ONE = TEST_USER+"-One"; + private static final String USER_TWO = TEST_USER+"-Two"; + private static final String USER_THREE = TEST_USER+"-Three"; + + private static final int SCALE_USERS = 5; + private static final int SCALE_WEBPROJECTS = 2; + + // + // services + // + + private WebProjectService wpService; + private SandboxService sbService; + private AuthenticationService authenticationService; + private PersonService personService; + + private AVMService avmLockingAwareService; + private AVMService avmNonLockingAwareService; + + + @Override + protected void setUp() throws Exception + { + // Get the required services + wpService = (WebProjectService)ctx.getBean("WebProjectService"); + sbService = (SandboxService)ctx.getBean("SandboxService"); + authenticationService = (AuthenticationService)ctx.getBean("AuthenticationService"); + personService = (PersonService)ctx.getBean("PersonService"); + + // WCM locking + avmLockingAwareService = (AVMService)ctx.getBean("AVMLockingAwareService"); + + // without WCM locking + avmNonLockingAwareService = (AVMService)ctx.getBean("AVMService"); + + // By default run as Admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + createUser(USER_ONE); + createUser(USER_TWO); + createUser(USER_THREE); + } + + @Override + protected void tearDown() throws Exception + { + if (CLEAN) + { + // Switch back to Admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + List webProjects = wpService.listWebProjects(); + for (WebProjectInfo wpInfo : webProjects) + { + if (wpInfo.getStoreId().startsWith(TEST_WEBPROJ_DNS)) + { + wpService.deleteWebProject(wpInfo.getNodeRef()); + } + } + + deleteUser(USER_ONE); + deleteUser(USER_TWO); + deleteUser(USER_THREE); + } + + AuthenticationUtil.clearCurrentSecurityContext(); + super.tearDown(); + } + + private void createUser(String userName) + { + if (authenticationService.authenticationExists(userName) == false) + { + authenticationService.createAuthentication(userName, "PWD".toCharArray()); + + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(ppOne); + } + } + + private void deleteUser(String userName) + { + if (authenticationService.authenticationExists(userName) == true) + { + personService.deletePerson(userName); + authenticationService.deleteAuthentication(userName); + } + } + + public void testSimple() + { + int storeCnt = avmLockingAwareService.getStores().size(); + + // create web project (also creates staging sandbox and admin's author sandbox) + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-simple", TEST_WEBPROJ_NAME+"-simple", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION, TEST_WEBPROJ_DEFAULT_WEBAPP, TEST_WEBPROJ_DONT_USE_AS_TEMPLATE, null); + String wpStoreId = wpInfo.getStoreId(); + + // list 2 sandboxes + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + // list 4 extra AVM stores (2 per sandbox) + assertEquals(storeCnt+4, avmLockingAwareService.getStores().size()); // 2x stating (main,preview), 2x admin author (main, preview) + + // get admin's sandbox + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + assertNotNull(sbInfo); + + // get staging sandbox + sbInfo = sbService.getStagingSandbox(wpStoreId); + assertNotNull(sbInfo); + + // invite user one to the web project and do not implicitly create user one's sandbox + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_PUBLISHER, false); + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + sbInfo = sbService.createAuthorSandbox(wpStoreId, USER_TWO); + assertEquals(3, sbService.listSandboxes(wpStoreId).size()); + + sbInfo = sbService.getSandbox(sbInfo.getSandboxId()); + sbService.deleteSandbox(sbInfo.getSandboxId()); + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + // delete admin's sandbox + sbService.deleteSandbox(sbService.getAuthorSandbox(wpStoreId).getSandboxId()); + assertEquals(1, sbService.listSandboxes(wpStoreId).size()); + + // delete web project (also deletes staging sandbox) + wpService.deleteWebProject(wpStoreId); + + assertEquals(storeCnt, avmLockingAwareService.getStores().size()); + } + + public void testCreateAuthorSandbox() + { + // Create a web project + WebProjectInfo wpInfo1 = wpService.createWebProject(TEST_WEBPROJ_DNS+"-create-author", TEST_WEBPROJ_NAME+"-author", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION, TEST_WEBPROJ_DEFAULT_WEBAPP, TEST_WEBPROJ_DONT_USE_AS_TEMPLATE, null); + + String expectedUserSandboxId = TEST_SANDBOX+"-create-author" + "--" + USER_ADMIN; + + SandboxInfo sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId()); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_ADMIN, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + + sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId(), USER_ONE); + assertNull(sbInfo1); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId()); + assertNull(sbInfo1); + + // Switch back to admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + // Invite web user + wpService.inviteWebUser(wpInfo1.getStoreId(), USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER); + + // Create author sandbox for user one - admin is the creator + sbInfo1 = sbService.createAuthorSandbox(wpInfo1.getStoreId(), USER_ONE); + + expectedUserSandboxId = TEST_SANDBOX+"-create-author" + "--" + USER_ONE; + + sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId(), USER_ONE); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_ONE, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + // Get author sandbox + sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId()); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_ONE, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + + String userSandboxId = sbInfo1.getSandboxId(); + + // Get (author) sandbox + sbInfo1 = sbService.getSandbox(userSandboxId); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_ONE, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + + // Should return same as before + sbInfo1 = sbService.createAuthorSandbox(wpInfo1.getStoreId()); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_ONE, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + try + { + // Try to create author sandbox as a non-web user (-ve test) + sbService.createAuthorSandbox(wpInfo1.getStoreId()); // ignore return + fail("Shouldn't be able to create author store since not a web user"); + } + catch (AccessDeniedException exception) + { + // Expected + } + + // Switch back to admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + // Invite web user + wpService.inviteWebUser(wpInfo1.getStoreId(), USER_TWO, WCMUtil.ROLE_CONTENT_REVIEWER); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + // Get author sandbox + sbInfo1 = sbService.getAuthorSandbox(wpInfo1.getStoreId()); + assertNull(sbInfo1); + + expectedUserSandboxId = TEST_SANDBOX+"-create-author" + "--" + USER_TWO; + + // Create own sandbox - user two is the creator + sbInfo1 = sbService.createAuthorSandbox(wpInfo1.getStoreId()); + checkSandboxInfo(sbInfo1, expectedUserSandboxId, USER_TWO, USER_TWO, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + } + + private void checkSandboxInfo(SandboxInfo sbInfo, String expectedStoreId, String expectedName, String expectedCreator, String expectedMainStoreName, QName expectedSandboxType) + { + assertNotNull(sbInfo); + assertEquals(expectedStoreId, sbInfo.getSandboxId()); + assertEquals(expectedName, sbInfo.getName()); + assertEquals(expectedCreator, sbInfo.getCreator()); + assertNotNull(sbInfo.getCreatedDate()); + assertEquals(expectedMainStoreName, sbInfo.getMainStoreName()); + assertEquals(expectedSandboxType, sbInfo.getSandboxType()); + } + + public void testListSandboxes() throws Exception + { + // Create web project - implicitly creates staging sandbox and also author sandbox for web project creator (in this case, admin) + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-list", TEST_WEBPROJ_NAME+" list", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + List sbInfos = sbService.listSandboxes(wpInfo.getStoreId()); + assertEquals(2, sbInfos.size()); // staging sandbox, author sandbox (for admin) + + String expectedUserSandboxId = TEST_SANDBOX+"-list" + "--" + USER_ADMIN; + + // Do detailed check of the sandbox info objects + for (SandboxInfo sbInfo : sbInfos) + { + QName sbType = sbInfo.getSandboxType(); + + if (sbType.equals(SandboxConstants.PROP_SANDBOX_STAGING_MAIN) == true) + { + checkSandboxInfo(sbInfo, TEST_SANDBOX+"-list", TEST_SANDBOX+"-list", USER_ADMIN, TEST_SANDBOX+"-list", SandboxConstants.PROP_SANDBOX_STAGING_MAIN); + } + else if (sbType.equals(SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN) == true) + { + checkSandboxInfo(sbInfo, expectedUserSandboxId, USER_ADMIN, USER_ADMIN, expectedUserSandboxId, SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN); + } + else + { + fail("The sandbox store id " + sbInfo.getSandboxId() + " is not recognised"); + } + } + + // TODO add more here + } + + public void testGetSandbox() + { + // Get a sandbox that isn't there + SandboxInfo sbInfo = sbService.getSandbox(TEST_SANDBOX+"-get"); + assertNull(sbInfo); + + // Create web project - implicitly creates staging sandbox and also admin sandbox (author sandbox for web project creator) + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-get", TEST_WEBPROJ_NAME+" get", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + // Get staging sandbox + sbInfo = sbService.getStagingSandbox(wpInfo.getStoreId()); + checkSandboxInfo(sbInfo, TEST_SANDBOX+"-get", TEST_SANDBOX+"-get", USER_ADMIN, TEST_SANDBOX+"-get", SandboxConstants.PROP_SANDBOX_STAGING_MAIN); + + // Get (staging) sandbox + String stagingSandboxId = wpInfo.getStagingStoreName(); + sbInfo = sbService.getSandbox(stagingSandboxId); + checkSandboxInfo(sbInfo, TEST_SANDBOX+"-get", TEST_SANDBOX+"-get", USER_ADMIN, TEST_SANDBOX+"-get", SandboxConstants.PROP_SANDBOX_STAGING_MAIN); + } + + public void testIsSandboxType() + { + // Get a sandbox that isn't there + SandboxInfo sbInfo = sbService.getSandbox(TEST_SANDBOX+"-is"); + assertNull(sbInfo); + + // Create web project - implicitly creates staging sandbox and also admin sandbox (author sandbox for web project creator) + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-is", TEST_WEBPROJ_NAME+" is", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + // Get staging sandbox + sbInfo = sbService.getStagingSandbox(wpInfo.getStoreId()); + + assertTrue(sbService.isSandboxType(sbInfo.getSandboxId(), SandboxConstants.PROP_SANDBOX_STAGING_MAIN)); + assertFalse(sbService.isSandboxType(sbInfo.getSandboxId(), SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)); + + // Get author sandbox + sbInfo = sbService.getAuthorSandbox(wpInfo.getStoreId()); + + assertTrue(sbService.isSandboxType(sbInfo.getSandboxId(), SandboxConstants.PROP_SANDBOX_AUTHOR_MAIN)); + assertFalse(sbService.isSandboxType(sbInfo.getSandboxId(), SandboxConstants.PROP_SANDBOX_STAGING_MAIN)); + } + + public void testDeleteSandbox() + { + // Create web project - implicitly creates staging sandbox and also admin sandbox (author sandbox for web project creator) + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-delete", TEST_WEBPROJ_NAME+" delete", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + String wpStoreId = wpInfo.getStoreId(); + + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + // Get staging sandbox + SandboxInfo sbInfo = sbService.getStagingSandbox(wpStoreId); + + try + { + // Try to delete staging sandbox (-ve test) + sbService.deleteSandbox(sbInfo.getSandboxId()); + fail("Shouldn't be able to delete staging sandbox"); + } + catch (AlfrescoRuntimeException exception) + { + // Expected + } + + try + { + // Try to delete non-existant sandbox (-ve test) + sbService.deleteSandbox("some-random-staging-sandbox"); + fail("Shouldn't be able to delete non-existant sandbox"); + } + catch (AVMNotFoundException exception) + { + // Expected + } + + // Get admin author sandbox + sbInfo = sbService.getAuthorSandbox(wpInfo.getStoreId()); + sbService.deleteSandbox(sbInfo.getSandboxId()); + + assertEquals(1, sbService.listSandboxes(wpInfo.getStoreId()).size()); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER); + wpService.inviteWebUser(wpStoreId, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER, true); + + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + sbService.createAuthorSandbox(wpStoreId, USER_ONE); + sbService.createAuthorSandbox(wpStoreId, USER_TWO); + + assertEquals(4, sbService.listSandboxes(wpStoreId).size()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + // NOTE: content publisher can list other sandboxes + assertEquals(4, sbService.listSandboxes(wpStoreId).size()); + + sbInfo = sbService.getAuthorSandbox(wpStoreId); + assertNotNull(sbInfo); + + // can delete own sandbox + sbService.deleteSandbox(sbInfo.getSandboxId()); + + assertEquals(3, sbService.listSandboxes(wpStoreId).size()); + + sbInfo = sbService.getAuthorSandbox(wpStoreId); + assertNull(sbInfo); + + // but not others + try + { + // Try to delete another author's sandbox as a non-content manager (-ve test) + sbService.deleteSandbox(wpInfo.getStoreId()+"--"+USER_THREE); + fail("Shouldn't be able to delete another author's sandbox"); + } + catch (AccessDeniedException exception) + { + // Expected + } + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + assertEquals(3, sbService.listSandboxes(wpStoreId).size()); + + // content manager can delete others + sbInfo = sbService.getAuthorSandbox(wpStoreId, USER_THREE); + sbService.deleteSandbox(sbInfo.getSandboxId()); + + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + } + + // list changed (in this test, new) items in user sandbox compared to staging sandbox + public void testListNewItems1() + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-listNewItems1", TEST_WEBPROJ_NAME+" listNewItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + String wpStoreId = wpInfo.getStoreId(); + + assertEquals(2, sbService.listSandboxes(wpStoreId).size()); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR); + sbService.createAuthorSandbox(wpStoreId, USER_ONE); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String sbStoreId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(sbStoreId, true); + assertEquals(0, items.size()); + + String authorSandboxRootPath = sbStoreId + ":/"; + String authorSandboxWebppPath = sbStoreId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + wpInfo.getDefaultWebApp(); + + avmLockingAwareService.createFile(authorSandboxRootPath, "myFile1"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(1, items.size()); + assertEquals("myFile1", items.get(0).getName()); + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1", "myFile2"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath+"/myDir1", "myDir2"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1/myDir2", "myFile3"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1/myDir2", "myFile4"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath+"/myDir1", "myDir3"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(2, items.size()); // new dir with new dirs/files is returned as single change + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myFile1") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + items = sbService.listChangedItemsWebApp(sbStoreId, wpInfo.getDefaultWebApp(), false); + assertEquals(1, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + items = sbService.listChangedItemsDir(authorSandboxWebppPath+"/myDir1", false); + assertEquals(1, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + } + + // list changed (in this test, new) items in two different user sandboxes compared to each other + public void testListNewItems2() + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-listNewItems2", TEST_WEBPROJ_NAME+" listNewItems2", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + String wpStoreId = wpInfo.getStoreId(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo1 = sbService.getAuthorSandbox(wpStoreId); + String sbStoreId = sbInfo1.getSandboxId(); + + List items = sbService.listChangedItems(sbStoreId, true); + assertEquals(0, items.size()); + + String authorSandboxRootPath = sbStoreId + ":/"; + + avmLockingAwareService.createFile(authorSandboxRootPath, "myFile1"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(1, items.size()); + assertEquals("myFile1", items.get(0).getName()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + SandboxInfo sbInfo2 = sbService.getAuthorSandbox(wpStoreId); + sbStoreId = sbInfo2.getSandboxId(); + + items = sbService.listChangedItems(sbStoreId, true); + assertEquals(0, items.size()); + + authorSandboxRootPath = sbStoreId + ":/"; + + avmLockingAwareService.createFile(authorSandboxRootPath, "myFile2"); + avmLockingAwareService.createFile(authorSandboxRootPath, "myFile3"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(2, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myFile2") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile3") && item.isFile()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + // Switch back to admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + sbInfo1 = sbService.getAuthorSandbox(wpStoreId, USER_ONE); + sbInfo2 = sbService.getAuthorSandbox(wpStoreId, USER_TWO); + + items = sbService.listChangedItems(sbInfo1.getSandboxId()+":/", sbInfo2.getSandboxId()+":/", false); + assertEquals(1, items.size()); + assertEquals("myFile1", items.get(0).getName()); + + items = sbService.listChangedItems(sbInfo2.getSandboxId()+":/", sbInfo1.getSandboxId()+":/", false); + assertEquals(2, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myFile2") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile3") && item.isFile()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + } + + // list changed (in this test, new) items in two different user sandboxes compared to each other - without locking + public void testListNewItems3() + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-listNewItems2", TEST_WEBPROJ_NAME+" listNewItems2", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + String wpStoreId = wpInfo.getStoreId(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo1 = sbService.getAuthorSandbox(wpStoreId); + String sbStoreId = sbInfo1.getSandboxId(); + + List items = sbService.listChangedItems(sbStoreId, true); + assertEquals(0, items.size()); + + String authorSandboxRootPath = sbStoreId + ":/"; + + avmNonLockingAwareService.createFile(authorSandboxRootPath, "myFile1"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(1, items.size()); + assertEquals("myFile1", items.get(0).getName()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + SandboxInfo sbInfo2 = sbService.getAuthorSandbox(wpStoreId); + sbStoreId = sbInfo2.getSandboxId(); + + items = sbService.listChangedItems(sbStoreId, true); + assertEquals(0, items.size()); + + authorSandboxRootPath = sbStoreId + ":/"; + + avmNonLockingAwareService.createFile(authorSandboxRootPath, "myFile1"); // allowed, since using base (non-locking-aware) AVM service + avmNonLockingAwareService.createFile(authorSandboxRootPath, "myFile2"); + avmNonLockingAwareService.createFile(authorSandboxRootPath, "myFile3"); + + items = sbService.listChangedItems(sbStoreId, false); + assertEquals(3, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myFile1") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile2") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile3") && item.isFile()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + // Switch back to admin + AuthenticationUtil.setCurrentUser(USER_ADMIN); + + sbInfo1 = sbService.getAuthorSandbox(wpStoreId, USER_ONE); + sbInfo2 = sbService.getAuthorSandbox(wpStoreId, USER_TWO); + + items = sbService.listChangedItems(sbInfo1.getSandboxId()+":/", sbInfo2.getSandboxId()+":/", false); + assertEquals(1, items.size()); + assertEquals("myFile1", items.get(0).getName()); + + items = sbService.listChangedItems(sbInfo2.getSandboxId()+":/", sbInfo1.getSandboxId()+":/", false); + assertEquals(3, items.size()); + + for (AVMNodeDescriptor item : items) + { + if (item.getName().equals("myFile1") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile2") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myFile3") && item.isFile()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + } + + // submit new items in user sandbox to staging sandbox + public void testSubmitNewItems1() + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitNewItems1", TEST_WEBPROJ_NAME+" submitNewItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + String wpStoreId = wpInfo.getStoreId(); + String webApp = wpInfo.getDefaultWebApp(); + String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Invite web user + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + avmLockingAwareService.createFile(authorSandboxWebppPath, "myFile1"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1", "myFile2"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath+"/myDir1", "myDir2"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1/myDir2", "myFile3"); + avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1/myDir2", "myFile4"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath+"/myDir1", "myDir3"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); // new dir with new dirs/files is returned as single change + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + + for (AVMNodeDescriptor item : listing.values()) + { + if (item.getName().equals("myFile1") && item.isFile()) + { + continue; + } + else if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + } + + // submit changed items in user sandbox to staging sandbox + public void testSubmitChangedItems1() throws IOException + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitChangedItems1", TEST_WEBPROJ_NAME+" submitChangedItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String webApp = wpInfo.getDefaultWebApp(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + final String MYFILE1 = "This is myFile1"; + OutputStream out = avmLockingAwareService.createFile(authorSandboxWebppPath, "myFile1"); + byte [] buff = MYFILE1.getBytes(); + out.write(buff); + out.close(); + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + + final String MYFILE2 = "This is myFile2"; + out = avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1", "myFile2"); + buff = MYFILE2.getBytes(); + out.write(buff); + out.close(); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + sbInfo = sbService.getAuthorSandbox(wpStoreId); + authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + final String MYFILE1_MODIFIED = "This is myFile1 ... modified by "+USER_TWO; + out = avmLockingAwareService.getFileOutputStream(authorSandboxWebppPath+"/myFile1"); + buff = (MYFILE1_MODIFIED).getBytes(); + out.write(buff); + out.close(); + + final String MYFILE2_MODIFIED = "This is myFile2 ... modified by "+USER_TWO; + out = avmLockingAwareService.getFileOutputStream(authorSandboxWebppPath+"/myDir1/myFile2"); + buff = (MYFILE2_MODIFIED).getBytes(); + out.write(buff); + out.close(); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); + + // check staging before + stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + InputStream in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myFile1"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE1, new String(buff, 0, MYFILE1.length())); // assumes 1byte=1char + + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myDir1/myFile2"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE2, new String(buff, 0, MYFILE2.length())); + + // submit (modified items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, null, null); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myFile1"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE1_MODIFIED, new String(buff, 0, MYFILE1_MODIFIED.length())); + + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myDir1/myFile2"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE2_MODIFIED, new String(buff, 0, MYFILE1_MODIFIED.length())); + } + + // submit deleted items in user sandbox to staging sandbox + public void testSubmitDeletedItems1() throws IOException + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-submitDeletedItems1", TEST_WEBPROJ_NAME+" submitDeletedItems1", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String webApp = wpInfo.getDefaultWebApp(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + final String MYFILE1 = "This is myFile1"; + OutputStream out = avmLockingAwareService.createFile(authorSandboxWebppPath, "myFile1"); + byte [] buff = MYFILE1.getBytes(); + out.write(buff); + out.close(); + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath+"/myDir1", "myDir2"); + + final String MYFILE2 = "This is myFile2"; + out = avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1", "myFile2"); + buff = MYFILE2.getBytes(); + out.write(buff); + out.close(); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + sbInfo = sbService.getAuthorSandbox(wpStoreId); + authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + avmLockingAwareService.removeNode(authorSandboxWebppPath+"/myFile1"); + avmLockingAwareService.removeNode(authorSandboxWebppPath+"/myDir1/myDir2"); + + // do not list deleted + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // do list deleted + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, true); + assertEquals(2, items.size()); + + // check staging before + stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myFile1")); + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1")); + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1/myDir2")); + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1/myFile2")); + + // submit (deleted items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, null, null); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + assertNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myFile1")); + assertNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1/myDir2")); + + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1")); + assertNotNull(avmLockingAwareService.lookup(-1, stagingSandboxWebppPath+"/myDir1/myFile2")); + } + + // revert all (changed) items in user sandbox + public void testRevertAll() throws IOException + { + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-revertChangedItems", TEST_WEBPROJ_NAME+" revertChangedItems", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String webApp = wpInfo.getDefaultWebApp(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, true); + wpService.inviteWebUser(wpStoreId, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + final String MYFILE1 = "This is myFile1"; + OutputStream out = avmLockingAwareService.createFile(authorSandboxWebppPath, "myFile1"); + byte [] buff = MYFILE1.getBytes(); + out.write(buff); + out.close(); + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + + final String MYFILE2 = "This is myFile2"; + out = avmLockingAwareService.createFile(authorSandboxWebppPath+"/myDir1", "myFile2"); + buff = MYFILE2.getBytes(); + out.write(buff); + out.close(); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + + // Switch to USER_TWO + AuthenticationUtil.setCurrentUser(USER_TWO); + + sbInfo = sbService.getAuthorSandbox(wpStoreId); + authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + final String MYFILE1_MODIFIED = "This is myFile1 ... modified by "+USER_TWO; + out = avmLockingAwareService.getFileOutputStream(authorSandboxWebppPath+"/myFile1"); + buff = (MYFILE1_MODIFIED).getBytes(); + out.write(buff); + out.close(); + + final String MYFILE2_MODIFIED = "This is myFile2 ... modified by "+USER_TWO; + out = avmLockingAwareService.getFileOutputStream(authorSandboxWebppPath+"/myDir1/myFile2"); + buff = (MYFILE2_MODIFIED).getBytes(); + out.write(buff); + out.close(); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(2, items.size()); + + // check staging before + stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + InputStream in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myFile1"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE1, new String(buff, 0, MYFILE1.length())); // assumes 1byte = 1char + + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myDir1/myFile2"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE2, new String(buff, 0, MYFILE2.length())); + + // revert (modified items) ! + sbService.revertAllWebApp(authorSandboxId, webApp); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myFile1"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE1, new String(buff, 0, MYFILE1.length())); + + in = avmLockingAwareService.getFileInputStream(-1, stagingSandboxWebppPath+"/myDir1/myFile2"); + buff = new byte[1024]; + in.read(buff); + in.close(); + assertEquals(MYFILE2, new String(buff, 0, MYFILE2.length())); + } + + public void testListSnapshots() throws IOException + { + Date fromDate = new Date(); + + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-listSnapshots", TEST_WEBPROJ_NAME+" listSnapshots", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String webApp = wpInfo.getDefaultWebApp(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir2"); + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir3"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(3, items.size()); + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + List sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(0, sbVersions.size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(3, listing.size()); + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(1, sbVersions.size()); + + // more changes ... + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir4"); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + // check staging after + listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(4, listing.size()); + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(2, sbVersions.size()); + } + + public void testRevertSnapshot() throws IOException + { + Date fromDate = new Date(); + + WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-revertSnapshot", TEST_WEBPROJ_NAME+" revertSnapshot", TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); + + final String wpStoreId = wpInfo.getStoreId(); + final String webApp = wpInfo.getDefaultWebApp(); + final String stagingSandboxId = wpInfo.getStagingStoreName(); + + // Start: Test ETWOTWO-817 + + // Invite web users + wpService.inviteWebUser(wpStoreId, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, true); + + // Switch to USER_ONE + AuthenticationUtil.setCurrentUser(USER_ONE); + + // Finish: Test ETWOTWO-817 + + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpStoreId); + String authorSandboxId = sbInfo.getSandboxId(); + + // no changes yet + List items = sbService.listChangedItems(authorSandboxId, true); + assertEquals(0, items.size()); + + String authorSandboxWebppPath = authorSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir1"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(1, items.size()); + + // check staging before + String stagingSandboxWebppPath = stagingSandboxId + ":" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE + "/" + webApp; + assertEquals(0, avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false).size()); + + List sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(0, sbVersions.size()); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + items = sbService.listChangedItemsWebApp(authorSandboxId, webApp, false); + assertEquals(0, items.size()); + + // check staging after + Map listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(1, listing.size()); + for (AVMNodeDescriptor item : listing.values()) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(1, sbVersions.size()); + + // more changes ... + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir2"); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + // check staging after + listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + for (AVMNodeDescriptor item : listing.values()) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else if (item.getName().equals("myDir2") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(2, sbVersions.size()); + + // more changes ... + avmLockingAwareService.createDirectory(authorSandboxWebppPath, "myDir3"); + + // submit (new items) ! + sbService.submitAllWebApp(authorSandboxId, webApp, "a submit label", "a submit comment"); + + // check staging after + listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(3, listing.size()); + for (AVMNodeDescriptor item : listing.values()) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else if (item.getName().equals("myDir2") && item.isDirectory()) + { + continue; + } + else if (item.getName().equals("myDir3") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(3, sbVersions.size()); + + // revert to snapshot ... + + VersionDescriptor version = sbVersions.get(1); + int versionId = version.getVersionID(); + + sbService.revertSnapshot(stagingSandboxId, versionId); + + sbVersions = sbService.listSnapshots(stagingSandboxId, fromDate, new Date(), false); + assertEquals(4, sbVersions.size()); + + // check staging after + listing = avmLockingAwareService.getDirectoryListing(-1, stagingSandboxWebppPath, false); + assertEquals(2, listing.size()); + for (AVMNodeDescriptor item : listing.values()) + { + if (item.getName().equals("myDir1") && item.isDirectory()) + { + continue; + } + else if (item.getName().equals("myDir2") && item.isDirectory()) + { + continue; + } + else + { + fail("The item '" + item.getName() + "' is not recognised"); + } + } + } + + public void testPseudoScaleTest() + { + long start = System.currentTimeMillis(); + + long split = start; + + for (int i = 1; i <= SCALE_USERS; i++) + { + createUser(TEST_USER+"-"+i); + } + + System.out.println("testPseudoScaleTest: created "+SCALE_USERS+" users in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + wpService.createWebProject(TEST_WEBPROJ_DNS+"-"+i, TEST_WEBPROJ_NAME+"-"+i, TEST_WEBPROJ_TITLE, TEST_WEBPROJ_DESCRIPTION); // ignore return + } + + System.out.println("testPseudoScaleTest: created "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + Map userRoles = new HashMap(SCALE_USERS); + for (int j = 1; j <= SCALE_USERS; j++) + { + userRoles.put(TEST_USER+"-"+j, WCMUtil.ROLE_CONTENT_MANAGER); + } + wpService.inviteWebUsersGroups(wpInfo.getNodeRef(), userRoles, true); + } + + System.out.println("testPseudoScaleTest: invited "+SCALE_USERS+" content managers (and created user sandboxes) to each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + assertEquals(SCALE_USERS+2, sbService.listSandboxes(wpInfo.getStoreId()).size()); // including staging sandbox and admin sandbox (web project creator) + } + + System.out.println("testPseudoScaleTest: list sandboxes for admin for each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + assertEquals(SCALE_USERS+1, wpService.listWebUsers(wpInfo.getStoreId()).size()); // including admin user (web project creator) + } + + System.out.println("testPseudoScaleTest: list web users for admin for each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + + for (int j = 1; j <= SCALE_USERS; j++) + { + AuthenticationUtil.setCurrentUser(TEST_USER+"-"+j); + assertEquals(SCALE_USERS+2, sbService.listSandboxes(wpInfo.getStoreId()).size()); // including staging sandbox and admin sandbox (web project creator) + } + AuthenticationUtil.setCurrentUser(USER_ADMIN); + } + + System.out.println("testPseudoScaleTest: list sandboxes for "+SCALE_USERS+" content managers for each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + + for (int j = 1; j <= SCALE_USERS; j++) + { + AuthenticationUtil.setCurrentUser(TEST_USER+"-"+j); + assertEquals(SCALE_USERS+1, wpService.listWebUsers(wpInfo.getStoreId()).size()); // including admin user (web project creator) + } + AuthenticationUtil.setCurrentUser(USER_ADMIN); + } + + System.out.println("testPseudoScaleTest: list web users for "+SCALE_USERS+" content managers for each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + + for (int j = 1; j <= SCALE_USERS; j++) + { + SandboxInfo sbInfo = sbService.getAuthorSandbox(wpInfo.getStoreId(), TEST_USER+"-"+j); + sbService.deleteSandbox(sbInfo.getSandboxId()); + } + } + + System.out.println("testPseudoScaleTest: deleted "+SCALE_USERS+" author sandboxes for each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_WEBPROJECTS; i++) + { + WebProjectInfo wpInfo = wpService.getWebProject(TEST_WEBPROJ_DNS+"-"+i); + wpService.deleteWebProject(wpInfo.getNodeRef()); + } + + System.out.println("testPseudoScaleTest: deleted "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); + + split = System.currentTimeMillis(); + + for (int i = 1; i <= SCALE_USERS; i++) + { + deleteUser(TEST_USER+"-"+i); + } + + System.out.println("testPseudoScaleTest: deleted "+SCALE_USERS+" users in "+(System.currentTimeMillis()-split)+" msecs"); + } + + + /* + // == Test the JavaScript API == + + public void testJSAPI() throws Exception + { + ScriptLocation location = new ClasspathScriptLocation("org/alfresco/wcm/script/test_sandboxService.js"); + scriptService.executeScript(location, new HashMap(0)); + } + */ +} diff --git a/source/java/org/alfresco/wcm/sandbox/script/Asset.java b/source/java/org/alfresco/wcm/sandbox/script/Asset.java new file mode 100644 index 0000000000..5fb9b243df --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/script/Asset.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox.script; + +import java.util.Date; + +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.util.ISO8601DateFormat; + +/** + * Asset in a sandbox exposed over Java Script API. + * @author mrogers + * + */ +public class Asset +{ + private AVMNodeDescriptor desc; + private Sandbox sandbox; + + public Asset(Sandbox sandbox, AVMNodeDescriptor desc) + { + this.sandbox = sandbox; + this.desc = desc; + } + + public String getCreator() + { + return desc.getCreator(); + } + + public Date getCreatedDate() + { + return new Date(desc.getCreateDate()); + } + + public String getCreatedDateAsISO8601() + { + return ISO8601DateFormat.format(getCreatedDate()); + } + + public String getModifier() + { + return desc.getLastModifier(); + } + + public Date getModifiedDate() + { + return new Date(desc.getModDate()); + } + + public String getModifiedDateAsISO8601() + { + return ISO8601DateFormat.format(getModifiedDate()); + } + + public String getAssetRef() + { + return desc.getGuid(); + } + + public String getName() + { + return desc.getName(); + } + + public String getPath() + { + return desc.getPath(); + } + + public boolean isFile() + { + return desc.isFile(); + } + + public boolean isDirectory() + { + return desc.isDirectory(); + } + + public boolean isDeleted() + { + return desc.isDeleted(); + } + + /** + * Get the parent sandbox which contains this asset + * @return the parent sandbox which contains this asset + */ + public Sandbox getSandbox() + { + return this.sandbox; + } +} diff --git a/source/java/org/alfresco/wcm/sandbox/script/Sandbox.java b/source/java/org/alfresco/wcm/sandbox/script/Sandbox.java new file mode 100644 index 0000000000..06baa71892 --- /dev/null +++ b/source/java/org/alfresco/wcm/sandbox/script/Sandbox.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.wcm.sandbox.script; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.jscript.ScriptableHashMap; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.wcm.sandbox.SandboxInfo; +import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.webproject.script.WebProject; + +/** + * Sandbox object to expose via JavaScript + * @author mrogers + * + */ +public class Sandbox implements Serializable +{ + /** + * serial version id + */ + private static final long serialVersionUID = -9176488061624800911L; + + private SandboxInfo si; + private WebProject webproject; + + /* + * Constructor from a SandboxInfo + */ + public Sandbox(WebProject webproject, SandboxInfo si) + { + this.webproject = webproject; + this.si = si; + } + + public void setName(String name) + { + // read only + } + + /** + * Display name for the sandbox + * @return the name of the sandbox + */ + public String getName() + { + return si.getName(); + } + + /** + * Set the unique reference for this sandbox - no-op, read only + * @param sandboxRef + */ + public void setSandboxRef(String sandboxRef) + { + // read only + } + + /** + * Submit the modified contents of this sandbox + */ + public void submitAll(String submitLabel, String submitComment) + { + getSandboxService().submitAll(getSandboxRef(), submitLabel, submitComment); + } + + /** + * Submit the modified contents of the webapp within this sandbox + */ + public void submitAllWebApp(String webApp, String submitLabel, String submitComment) + { + getSandboxService().submitAllWebApp(getSandboxRef(), webApp, submitLabel, submitComment); + } + + /** + * Revert all modified contents within this sandbox + */ + public void revertAll() + { + getSandboxService().revertAll(getSandboxRef()); + } + + /** + * Revert all modified contents within this sandbox + */ + public void revertAllWebApp(String webApp) + { + getSandboxService().revertAllWebApp(getSandboxRef(), webApp); + } + + /** + * Get the snapshots + * @param includeSystemGenerated + */ + public void getSnapshots(boolean includeSystemGenerated) + { + getSandboxService().listSnapshots(getSandboxRef(), includeSystemGenerated); + } + + /** + * Get the unique reference for this sandbox + */ + public String getSandboxRef() + { + return si.getSandboxId(); + } + + public String getCreator() + { + return si.getCreator(); + } + + public Date getCreatedDate() + { + return si.getCreatedDate(); + } + + public String getCreatedDateAsISO8601() + { + return ISO8601DateFormat.format(si.getCreatedDate()); + } + + /** + * Delete this sandbox + */ + public void deleteSandbox() + { + getSandboxService().deleteSandbox(getSandboxRef()); + } + + /* + * Save the updates to this sandbox + */ + public void save() + { + // no read-write params yet ... + } + + /** + * Get the store names + * @return the list of store names with the "main" store first. + */ + public String[] getStoreNames() + { + return si.getStoreNames(); + } + + /** + * Get the modified assets within this sandbox + * @return the list of changed assets + */ + public List getModifiedAssets() + { + List items = getSandboxService().listChangedItems(getSandboxRef(), true); + ArrayList ret = new ArrayList(items.size()); + + for(AVMNodeDescriptor item : items) + { + Asset a = new Asset(this, item); + ret.add(a); + } + return ret; + } + + /** + * Get the modified assets within this sandbox + * @return the list of changed assets + */ + public List getModifiedAssetsWebApp(String webApp) + { + List items = getSandboxService().listChangedItems(getSandboxRef(), webApp, true); + ArrayList ret = new ArrayList(items.size()); + + for(AVMNodeDescriptor item : items) + { + Asset a = new Asset(this, item); + ret.add(a); + } + return ret; + } + + /** + * Submit a list of files + */ + public void submitList(List toSubmit, String submitLabel, String submitComment) + { + // TODO - Interface will add string list + //ss.submitList(sbStoreId, items, submitLabel, submitComment) + } + + public List getAssets(String path) + { + return null; + } + + public WebProject getWebproject() + { + return this.webproject; + } + + private SandboxService getSandboxService() + { + return webproject.getWebProjects().getSandboxService(); + } +} diff --git a/source/java/org/alfresco/wcm/util/WCMUtil.java b/source/java/org/alfresco/wcm/util/WCMUtil.java index 96d5c4f567..7ff0aee148 100644 --- a/source/java/org/alfresco/wcm/util/WCMUtil.java +++ b/source/java/org/alfresco/wcm/util/WCMUtil.java @@ -25,15 +25,22 @@ package org.alfresco.wcm.util; import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.alfresco.config.JNDIConstants; import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.model.WCMAppModel; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.ParameterCheck; import org.alfresco.util.VirtServerUtils; import org.alfresco.wcm.sandbox.SandboxConstants; @@ -50,15 +57,15 @@ import org.alfresco.wcm.sandbox.SandboxConstants; public class WCMUtil { /** - * Extracts the store name from the avm path + * Extracts the sandbox store id from the avm path * * @param avmPath an absolute avm path * - * @return the store name + * @return the sandbox store id */ - protected static String getStoreName(final String avmPath) + protected static String getSandboxStoreId(final String avmPath) { - final int i = avmPath.indexOf(':'); + final int i = avmPath.indexOf(AVM_STORE_SEPARATOR); if (i == -1) { throw new IllegalArgumentException("path " + avmPath + " does not contain a store"); @@ -78,7 +85,7 @@ public class WCMUtil * * @return the web project store id */ - protected static String getStoreId(final String storeName) + protected static String getWebProjectStoreId(final String storeName) { final int index = storeName.indexOf(WCMUtil.STORE_SEPARATOR); return (index == -1 @@ -87,17 +94,17 @@ public class WCMUtil } /** - * Extracts the store id from the avm path + * Extracts the web project store id from the avm path * - * For example, if the avm path is: teststore--admin:/www/ROOT then the store id is: teststore + * For example, if the avm path is: teststore--admin:/www/ROOT then the web project id is: teststore * * @param avmPath an absolute avm path * - * @return the store id. + * @return the web project store id. */ - protected static String getStoreIdFromPath(final String avmPath) + protected static String getWebProjectStoreIdFromPath(final String avmPath) { - return getStoreId(getStoreName(avmPath)); + return getWebProjectStoreId(getSandboxStoreId(avmPath)); } /** @@ -109,7 +116,7 @@ public class WCMUtil */ protected static boolean isPreviewStore(final String storeName) { - return storeName.endsWith(WCMUtil.STORE_SEPARATOR + WCMUtil.STORE_PREVIEW); + return ((storeName != null) && (storeName.endsWith(WCMUtil.STORE_SEPARATOR + WCMUtil.STORE_PREVIEW))); } /** @@ -126,7 +133,7 @@ public class WCMUtil storeName = WCMUtil.getCorrespondingMainStoreName(storeName); } - return storeName.indexOf(STORE_SEPARATOR + STORE_WORKFLOW) != -1; + return ((storeName != null) && (storeName.indexOf(STORE_SEPARATOR + STORE_WORKFLOW) != -1)); } /** @@ -142,19 +149,19 @@ public class WCMUtil { storeName = WCMUtil.getCorrespondingMainStoreName(storeName); } - return storeName.indexOf(WCMUtil.STORE_SEPARATOR) != -1; + return ((storeName != null) && (storeName.indexOf(WCMUtil.STORE_SEPARATOR) != -1)); } /** - * Indicates whether the store name describes a main store. + * Indicates whether the store name describes a staging store. * * @param storeName the store name * * @return true if the store is a main store, false otherwise. */ - protected static boolean isMainStore(String storeName) + protected static boolean isStagingStore(String storeName) { - return (storeName.indexOf(WCMUtil.STORE_SEPARATOR) == -1); + return ((storeName != null) && (storeName.indexOf(WCMUtil.STORE_SEPARATOR) == -1)); } /** @@ -164,7 +171,7 @@ public class WCMUtil * * @return the username associated or null if this is a staging store. */ - protected static String getUserName(String storeName) + public static String getUserName(String storeName) { if (WCMUtil.isPreviewStore(storeName)) { @@ -175,6 +182,24 @@ public class WCMUtil ? null : storeName.substring(index + WCMUtil.STORE_SEPARATOR.length())); } + + /** + * Extracts the workflow id + * + * @param storeName + * @return + */ + public static String getWorkflowId(String storeName) + { + if (WCMUtil.isPreviewStore(storeName)) + { + storeName = WCMUtil.getCorrespondingMainStoreName(storeName); + } + final int index = storeName.indexOf(STORE_SEPARATOR + STORE_WORKFLOW); + return (index == -1 + ? null + : storeName.substring(index + WCMUtil.STORE_SEPARATOR.length())); + } /** * Returns the corresponding main store name if this is a preview store name. @@ -226,7 +251,7 @@ public class WCMUtil */ protected static String getCorrespondingPathInMainStore(final String avmPath) { - String storeName = WCMUtil.getStoreName(avmPath); + String storeName = WCMUtil.getSandboxStoreId(avmPath); storeName = WCMUtil.getCorrespondingMainStoreName(storeName); return WCMUtil.getCorrespondingPath(avmPath, storeName); } @@ -243,7 +268,7 @@ public class WCMUtil */ protected static String getCorrespondingPathInPreviewStore(final String avmPath) { - String storeName = WCMUtil.getStoreName(avmPath); + String storeName = WCMUtil.getSandboxStoreId(avmPath); storeName = WCMUtil.getCorrespondingPreviewStoreName(storeName); return WCMUtil.getCorrespondingPath(avmPath, storeName); } @@ -258,20 +283,19 @@ public class WCMUtil */ protected static String getCorrespondingPath(final String avmPath, final String otherStore) { - return (otherStore + ':' + WCMUtil.getStoreRelativePath(avmPath)); + return (otherStore + AVM_STORE_SEPARATOR + WCMUtil.getStoreRelativePath(avmPath)); } /** - * Returns the main staging store name for the specified store id. + * Returns the main staging store name for the specified web project * - * @param storeId store id to build staging store name for - * - * @return main staging store name for the specified store id + * @param wpStoreId web project store id to build staging store name for + * @return String main staging store name for the specified web project store id */ - protected static String buildStagingStoreName(final String storeId) + public static String buildStagingStoreName(final String wpStoreId) { - ParameterCheck.mandatoryString("storeId", storeId); - return storeId; + ParameterCheck.mandatoryString("wpStoreId", wpStoreId); + return wpStoreId; } /** @@ -295,7 +319,7 @@ public class WCMUtil * * @return the main store for the specified user and store id */ - protected static String buildUserMainStoreName(final String storeId, + public static String buildUserMainStoreName(final String storeId, final String userName) { ParameterCheck.mandatoryString("userName", userName); @@ -361,7 +385,7 @@ public class WCMUtil protected static String buildStoreRootPath(final String storeName) { ParameterCheck.mandatoryString("storeName", storeName); - return storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW; + return storeName + AVM_STORE_SEPARATOR + "/" + JNDIConstants.DIR_DEFAULT_WWW; } /** @@ -376,7 +400,7 @@ public class WCMUtil protected static String buildSandboxRootPath(final String storeName) { ParameterCheck.mandatoryString("storeName", storeName); - return storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW_APPBASE; + return storeName + AVM_STORE_SEPARATOR + JNDIConstants.DIR_DEFAULT_WWW_APPBASE; } /** @@ -393,9 +417,7 @@ public class WCMUtil return WCMUtil.buildSandboxRootPath(storeName) + '/' + webApp; } - // TODO refactor ... - // assume for now that it is a store name rather than a path - eg. main rather than main:/ - protected static String buildStoreUrl(AVMService avmService, String storeName, String domain, String port) + public static String buildStoreUrl(AVMService avmService, String storeName, String domain, String port) { ParameterCheck.mandatoryString("storeName", storeName); @@ -418,7 +440,7 @@ public class WCMUtil : buildStoreUrl(avmService, storeName, domain, port) + '/' + webApp); } - protected static String buildAssetUrl(String assetPath, String domain, String port, String dns) + public static String buildAssetUrl(String assetPath, String domain, String port, String dns) { ParameterCheck.mandatoryString("assetPath", assetPath); @@ -443,7 +465,7 @@ public class WCMUtil return MessageFormat.format(JNDIConstants.PREVIEW_ASSET_URL, dns, domain, port, assetPath); } - protected static String lookupStoreDNS(AVMService avmService, String store) + public static String lookupStoreDNS(AVMService avmService, String store) { ParameterCheck.mandatoryString("store", store); @@ -502,8 +524,8 @@ public class WCMUtil */ protected static String getStoreRelativePath(final String absoluteAVMPath) { - final Matcher m = STORE_RELATIVE_PATH_PATTERN.matcher(absoluteAVMPath); - return m.matches() && m.group(1).length() != 0 ? m.group(1) : null; + ParameterCheck.mandatoryString("absoluteAVMPath", absoluteAVMPath); + return absoluteAVMPath.substring(absoluteAVMPath.indexOf(AVM_STORE_SEPARATOR) + 1); } /** @@ -570,6 +592,24 @@ public class WCMUtil final Matcher m = SANDBOX_RELATIVE_PATH_PATTERN.matcher(absoluteAVMPath); return m.matches() && m.group(1).length() != 0 ? m.group(1) : null; } + + protected static Map listWebUsers(NodeService nodeService, NodeRef wpNodeRef) + { + List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); + + Map webUsers = new HashMap(23); + + for (ChildAssociationRef ref : userInfoRefs) + { + NodeRef userInfoRef = ref.getChildRef(); + String userName = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); + String userRole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); + + webUsers.put(userName, userRole); + } + + return webUsers; + } /** * Creates all directories for a path if they do not already exist. @@ -685,6 +725,8 @@ public class WCMUtil // Component Separator. protected static final String STORE_SEPARATOR = "--"; + protected static final char AVM_STORE_SEPARATOR = ':'; + // names of the stores representing the layers for an AVM website //XXXarielb this should be private protected final static String STORE_WORKFLOW = "workflow"; @@ -702,10 +744,6 @@ public class WCMUtil public static final String ROLE_CONTENT_REVIEWER = "ContentReviewer"; public static final String ROLE_CONTENT_CONTRIBUTOR = "ContentContributor"; - // pattern for absolute AVM Path - private final static Pattern STORE_RELATIVE_PATH_PATTERN = - Pattern.compile("[^:]+:(.+)"); - private final static Pattern WEBAPP_RELATIVE_PATH_PATTERN = Pattern.compile("([^:]+:/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/([^/]+))(.*)"); diff --git a/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java b/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java new file mode 100644 index 0000000000..0a65729dd1 --- /dev/null +++ b/source/java/org/alfresco/wcm/util/WCMWorkflowUtil.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.wcm.util; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import org.alfresco.model.WCMWorkflowModel; +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.LayeringDescriptor; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * WCM Specific workflow related helper methods. + * + * @author Ariel Backenroth + * @author Kevin Roast + * @author janv + */ +public class WCMWorkflowUtil +{ + private static final Log logger = LogFactory.getLog(WCMWorkflowUtil.class); + + public static List getAssociatedTasksForSandbox(WorkflowService workflowService, final String storeName) + { + String fromPath = WCMUtil.buildStoreRootPath(storeName); + WorkflowTaskQuery query = new WorkflowTaskQuery(); + + HashMap props = new HashMap(1, 1.0f); + + props.put(WCMWorkflowModel.PROP_FROM_PATH, fromPath); + query.setProcessCustomProps(props); + query.setActive(true); + + List tasks = workflowService.queryTasks(query); + + if (logger.isDebugEnabled()) + { + logger.debug("found " + tasks.size() + " tasks originating user sandbox " + fromPath); + } + + return tasks; + } + + public static List getAssociatedTasksForNode(AVMService avmService, AVMNodeDescriptor node, List tasks) + { + List result = new LinkedList(); + + for (WorkflowTask task : tasks) + { + final NodeRef ref = task.path.instance.workflowPackage; + final String path = WCMUtil.getCorrespondingPath(node.getPath(), ref.getStoreRef().getIdentifier()); + + if (logger.isDebugEnabled()) + { + logger.debug("checking store " + ref.getStoreRef().getIdentifier() + + " for " + node.getPath() + " (" + path + ")"); + } + + try + { + final LayeringDescriptor ld = avmService.getLayeringInfo(-1, path); + if (!ld.isBackground()) + { + if (logger.isDebugEnabled()) + { + logger.debug(path + " is in the foreground. workflow active"); + } + + result.add(task); + } + } + catch (final AVMNotFoundException avmnfe) + { + if (logger.isDebugEnabled()) + { + logger.debug(path + " not found"); + } + } + } + + return result; + } + + public static List getAssociatedTasksForNode(WorkflowService workflowService, AVMService avmService, AVMNodeDescriptor node) + { + final List tasks = WCMWorkflowUtil.getAssociatedTasksForSandbox(workflowService, WCMUtil.getSandboxStoreId(node.getPath())); + return getAssociatedTasksForNode(avmService, node, tasks); + } +} diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectInfoImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectInfoImpl.java index a1436c5a51..dc33711a31 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectInfoImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectInfoImpl.java @@ -32,7 +32,7 @@ import org.alfresco.wcm.util.WCMUtil; * * @author janv */ -public class WebProjectInfoImpl extends WCMUtil implements WebProjectInfo +public class WebProjectInfoImpl implements WebProjectInfo { /** Web Project node reference */ private NodeRef nodeRef; diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectService.java b/source/java/org/alfresco/wcm/webproject/WebProjectService.java index e4a9f8c493..4991e4906c 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectService.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectService.java @@ -347,9 +347,11 @@ public interface WebProjectService * Invite users/groups to web project *

* Note: authority name can be user or group, although a group is flattened into a set of users + *

+ * Note: author sandbox will NOT be auto created for each invited user * - * @param wpStoreId web project store id - * @param userGroupRoles map of pairs + * @param wpStoreId web project store id + * @param userGroupRoles map of pairs */ public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles); @@ -358,46 +360,83 @@ public interface WebProjectService *

* Note: authority name can be user or group, although a group is flattened into a set of users * - * @param wpNodeRef web project node ref - * @param userGroupRoles map of pairs + * @param wpStoreId web project store id + * @param userGroupRoles map of pairs + * @param autoCreateAuthorSandbox if true then auto create an author sandbox for each invited user */ - public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles); + public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles, boolean autoCreateAuthorSandbox); /** - * Invite user to web project + * Invite users/groups to web project + *

+ * Note: authority name can be user or group, although a group is flattened into a set of users * - * @param wpStoreId web project store id - * @param userName user name (not a group) - * @param userRole web project role + * @param wpNodeRef web project node ref + * @param userGroupRoles map of pairs + * @param autoCreateAuthorSandbox if true then auto create the author sandbox for each invited user + */ + public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles, boolean autoCreateAuthorSandbox); + + /** + * Invite user to web project + *

+ * Note: author sandbox will NOT be auto created for each invited user + * + * @param wpStoreId web project store id + * @param userName user name (not a group) + * @param userRole web project role */ public void inviteWebUser(String wpStoreId, String userName, String userRole); /** * Invite user to web project * - * @param wpNodeRef web project node ref - * @param userName user name (not a group) - * @param userRole web project role + * @param wpStoreId web project store id + * @param userName user name (not a group) + * @param userRole web project role + * @param autoCreateAuthorSandbox if true then auto create the author sandbox for each invited user */ - public void inviteWebUser(NodeRef wpNodeRef, String userName, String userRole); + public void inviteWebUser(String wpStoreId, String userName, String userRole, boolean autoCreateAuthorSandbox); + + /** + * Invite user to web project + * + * @param wpNodeRef web project node ref + * @param userName user name (not a group) + * @param userRole web project role + * @param autoCreateAuthorSandbox if true then auto create the author sandbox for each invited user + */ + public void inviteWebUser(NodeRef wpNodeRef, String userName, String userRole, boolean autoCreateAuthorSandbox); /** * Uninvite user from a web project *

- * Note: this will cascade delete the user's sandboxes without warning (even if there are modified items) + * Note: author sandbox will NOT be auto deleted * - * @param wpStoreId web project store id - * @param userName user name + * @param wpStoreId web project store id + * @param userName user name */ public void uninviteWebUser(String wpStoreId, String userName); /** * Uninvite user from a web project *

- * Note: this will cascade delete the user's sandboxes without warning (even if there are modified items) + * Note: if author sandbox is auto deleted then this will cascade delete without warning (even if there are changed items) * - * @param wpNodeRef web project node ref - * @param userName user name + * @param wpStoreId web project store id + * @param userName user name + * @param autoDeleteAuthorSandbox if true then auto delete the author sandbox */ - public void uninviteWebUser(NodeRef wpNodeRef, String userName); + public void uninviteWebUser(String wpStoreId, String userName, boolean autoDeleteAuthorSandbox); + + /** + * Uninvite user from a web project + *

+ * Note: if author sandbox is auto deleted then this will cascade delete without warning (even if there are changed items) + * + * @param wpNodeRef web project node ref + * @param userName user name + * @param autoDeleteAuthorSandbox if true then auto delete the author sandbox + */ + public void uninviteWebUser(NodeRef wpNodeRef, String userName, boolean autoDeleteAuthorSandbox); } diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java index 297f83781b..e41212e1e3 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java @@ -53,8 +53,8 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthorityService; @@ -220,7 +220,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } // create the AVM staging store to represent the newly created location website - sandboxFactory.createStagingSandbox(wpStoreId, wpNodeRef, branchStoreId); // ignore return + sandboxFactory.createStagingSandbox(wpStoreId, wpNodeRef, branchStoreId); // ignore return, fails if web project already exists // create the default webapp folder under the hidden system folders if (branchStoreId == null) @@ -246,7 +246,9 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService // break the permissions inheritance on the web project node so that only assigned users can access it permissionService.setInheritParentPermissions(wpNodeRef, false); - inviteWebUser(wpNodeRef, AuthenticationUtil.getCurrentEffectiveUserName(), WCMUtil.ROLE_CONTENT_MANAGER); + // TODO: Currently auto-creates author sandbox for creator of web project (eg. an admin or a DM contributor to web projects root space) + // NOTE: JSF client does not yet allow explicit creation of author sandboxes + inviteWebUser(wpNodeRef, AuthenticationUtil.getCurrentEffectiveUserName(), WCMUtil.ROLE_CONTENT_MANAGER, true); // Bind the post-commit transaction listener with data required for virtualization server notification CreateWebProjectTransactionListener tl = new CreateWebProjectTransactionListener(wpStoreId); @@ -624,27 +626,19 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); - // get the list of users who have a sandbox in the website - final Map existingUserRoles = listWebUsers(wpNodeRef); - AuthenticationUtil.runAs(new RunAsWork() { public Object doWork() throws Exception { - for (Map.Entry userRole : existingUserRoles.entrySet()) + List sbInfos = sandboxFactory.listSandboxes(wpStoreId, AuthenticationUtil.getSystemUserName()); + + for (SandboxInfo sbInfo : sbInfos) { - String username = userRole.getKey(); - - // delete the preview store for this user - deleteStore(WCMUtil.buildUserPreviewStoreName(wpStoreId, username)); - - // delete the main store for this user - deleteStore(WCMUtil.buildUserMainStoreName(wpStoreId, username)); + // delete sandbox + sandboxFactory.deleteSandbox(sbInfo.getSandboxId()); } - - // remove the main staging and preview stores - deleteStore(WCMUtil.buildStagingPreviewStoreName(wpStoreId)); - deleteStore(WCMUtil.buildStagingStoreName(wpStoreId)); + + // TODO delete workflow sandboxes ! // delete the web project node itself nodeService.deleteNode(wpNodeRef); @@ -659,20 +653,6 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } } - - /** - * Delete a store, checking for its existence first. - * - * @param store - */ - private void deleteStore(final String store) - { - // check it exists before we try to remove it - if (avmService.getStore(store) != null) - { - avmService.purgeStore(store); - } - } /* (non-Javadoc) * @see org.alfresco.wcm.webproject.WebProjectService#isContentManager(java.lang.String) @@ -687,7 +667,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public boolean isContentManager(String storeName, String userName) { - String wpStoreId = WCMUtil.getStoreId(storeName); + String wpStoreId = WCMUtil.getWebProjectStoreId(storeName); PropertyValue pValue = avmService.getStoreProperty(wpStoreId, SandboxConstants.PROP_WEB_PROJECT_NODE_REF); if (pValue != null) @@ -723,7 +703,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public boolean isWebUser(String storeName, String username) { - String wpStoreId = WCMUtil.getStoreId(storeName); + String wpStoreId = WCMUtil.getWebProjectStoreId(storeName); PropertyValue pValue = avmService.getStoreProperty(wpStoreId, SandboxConstants.PROP_WEB_PROJECT_NODE_REF); if (pValue != null) @@ -742,7 +722,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public boolean isWebUser(NodeRef wpNodeRef, String userName) { - String userRole = getWebUserRole(wpNodeRef, userName); + String userRole = getWebUserRoleImpl(wpNodeRef, userName); return (userRole != null); } @@ -751,7 +731,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public int getWebUserCount(NodeRef wpNodeRef) { - return listWebUserRefs(wpNodeRef).size(); + return WCMUtil.listWebUsers(nodeService, wpNodeRef).size(); } /* (non-Javadoc) @@ -767,33 +747,16 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public Map listWebUsers(NodeRef wpNodeRef) { - if (isContentManager(wpNodeRef)) + // special case: allow System - eg. to allow user to create their own sandbox on-demand (createAuthorSandbox) + if (isContentManager(wpNodeRef) || (AuthenticationUtil.getCurrentEffectiveUserName().equals(AuthenticationUtil.getSystemUserName()))) { - List userInfoRefs = listWebUserRefs(wpNodeRef); - - Map webUsers = new HashMap(23); - - for (ChildAssociationRef ref : userInfoRefs) - { - NodeRef userInfoRef = ref.getChildRef(); - String userName = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERNAME); - String userRole = (String)nodeService.getProperty(userInfoRef, WCMAppModel.PROP_WEBUSERROLE); - - webUsers.put(userName, userRole); - } - - return webUsers; + return WCMUtil.listWebUsers(nodeService, wpNodeRef); } else { throw new AccessDeniedException("Only content managers may list users in a web project"); } } - - private List listWebUserRefs(NodeRef wpNodeRef) - { - return nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); - } /* (non-Javadoc) * @see org.alfresco.wcm.webproject.WebProjectService#getWebUserRole(java.lang.String, java.lang.String) @@ -808,7 +771,6 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public String getWebUserRole(NodeRef wpNodeRef, String userName) { - long start = System.currentTimeMillis(); String userRole = null; if (! isWebProject(wpNodeRef)) @@ -824,35 +786,45 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } else { - StringBuilder query = new StringBuilder(128); - query.append("+PARENT:\"").append(wpNodeRef).append("\" "); - query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" "); - query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\""); - query.append(userName); - query.append("\""); - - ResultSet resultSet = searchService.query( - WEBPROJECT_STORE, - SearchService.LANGUAGE_LUCENE, - query.toString()); - List nodes = resultSet.getNodeRefs(); + userRole = getWebUserRoleImpl(wpNodeRef, userName); + } - if (nodes.size() == 1) + return userRole; + } + + private String getWebUserRoleImpl(NodeRef wpNodeRef, String userName) + { + long start = System.currentTimeMillis(); + String userRole = null; + + StringBuilder query = new StringBuilder(128); + query.append("+PARENT:\"").append(wpNodeRef).append("\" "); + query.append("+TYPE:\"").append(WCMAppModel.TYPE_WEBUSER).append("\" "); + query.append("+@").append(NamespaceService.WCMAPP_MODEL_PREFIX).append("\\:username:\""); + query.append(userName); + query.append("\""); + + ResultSet resultSet = searchService.query( + WEBPROJECT_STORE, + SearchService.LANGUAGE_LUCENE, + query.toString()); + List nodes = resultSet.getNodeRefs(); + + if (nodes.size() == 1) + { + userRole = (String)nodeService.getProperty(nodes.get(0), WCMAppModel.PROP_WEBUSERROLE); + } + else if (nodes.size() == 0) + { + if (logger.isTraceEnabled()) { - userRole = (String)nodeService.getProperty(nodes.get(0), WCMAppModel.PROP_WEBUSERROLE); - } - else if (nodes.size() == 0) - { - if (logger.isTraceEnabled()) - { - logger.trace("getWebProjectUserRole: user role not found for " + userName); - } - } - else - { - logger.warn("getWebProjectUserRole: more than one user role found for " + userName); + logger.trace("getWebProjectUserRole: user role not found for " + userName); } } + else + { + logger.warn("getWebProjectUserRole: more than one user role found for " + userName); + } if (logger.isTraceEnabled()) { @@ -867,7 +839,7 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public NodeRef findWebProjectNodeFromPath(String absoluteAVMPath) { - return findWebProjectNodeFromStore(WCMUtil.getStoreIdFromPath(absoluteAVMPath)); + return findWebProjectNodeFromStore(WCMUtil.getWebProjectStoreIdFromPath(absoluteAVMPath)); } /*(non-Javadoc) @@ -938,13 +910,18 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles) { - inviteWebUsersGroups(findWebProjectNodeFromStore(wpStoreId), userGroupRoles); + inviteWebUsersGroups(findWebProjectNodeFromStore(wpStoreId), userGroupRoles, false); } /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsers(org.alfresco.service.cmr.repository.NodeRef, java.util.Map) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUsersGroups(java.lang.String, java.util.Map, boolean) */ - public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles) + public void inviteWebUsersGroups(String wpStoreId, Map userGroupRoles, boolean autoCreateAuthorSandbox) + { + inviteWebUsersGroups(findWebProjectNodeFromStore(wpStoreId), userGroupRoles, autoCreateAuthorSandbox); + } + + public void inviteWebUsersGroups(NodeRef wpNodeRef, Map userGroupRoles, boolean autoCreateAuthorSandbox) { if (! isContentManager(wpNodeRef)) { @@ -954,13 +931,11 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService WebProjectInfo wpInfo = getWebProject(wpNodeRef); String wpStoreId = wpInfo.getStoreId(); - // create a sandbox for each user appropriately with permissions based on role // build a list of managers who will have full permissions on ALL staging areas List managers = new ArrayList(4); Set existingUsers = new HashSet(8); - // website already exists - we are only adding to the existing sandboxes - // so retrieve the list of managers from the existing users and the selected invitees + // retrieve the list of managers from the existing users for (Map.Entry userRole : userGroupRoles.entrySet()) { String authority = userRole.getKey(); @@ -990,9 +965,6 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService existingUsers.add(username); } - // build the sandboxes now we have the manager list and complete user list - // and create an association to a node to represent each invited user - List sandboxInfoList = new LinkedList(); int invitedCount = 0; @@ -1005,11 +977,14 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService for (String userAuth : findNestedUserAuthorities(authority)) { - // create the sandbox if the invited user does not already have one if (existingUsers.contains(userAuth) == false) { - SandboxInfo info = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); - sandboxInfoList.add(info); + if (autoCreateAuthorSandbox) + { + // create a sandbox for the user with permissions based on role + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); + sandboxInfoList.add(sbInfo); + } sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); @@ -1029,19 +1004,13 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); + AlfrescoTransactionSupport.bindListener(tl); + if (managersUpdateRequired == true) { - // walk existing sandboxes and reapply manager permissions to include any new manager users - for (Map.Entry userRole : existingUserRoles.entrySet()) - { - String username = userRole.getKey(); - if (existingUsers.contains(username)) - { - // only need to modify the sandboxes we haven't just created - sandboxFactory.updateSandboxManagers(wpStoreId, managers, username); - } - } - sandboxFactory.updateStagingAreaManagers(wpStoreId, wpNodeRef, managers); + sandboxFactory.updateSandboxManagers(wpStoreId, wpNodeRef, managers); } // get permissions and roles for a web project folder type @@ -1066,10 +1035,6 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } - // Bind the post-commit transaction listener with data required for virtualization server notification - InviteWebUsersTransactionListener tl = new InviteWebUsersTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); - AlfrescoTransactionSupport.bindListener(tl); - if (logger.isInfoEnabled()) { logger.info("Invited "+invitedCount+" web users (store id: "+wpStoreId+")"); @@ -1081,13 +1046,21 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void inviteWebUser(String wpStoreId, String userAuth, String role) { - inviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth, role); + inviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth, role, false); } /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(java.lang.String, java.lang.String, java.lang.String, boolean) */ - public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role) + public void inviteWebUser(String wpStoreId, String userAuth, String role, boolean autoCreateAuthorSandbox) + { + inviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth, role, autoCreateAuthorSandbox); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#inviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, boolean) + */ + public void inviteWebUser(NodeRef wpNodeRef, String userAuth, String role, boolean autoCreateAuthorSandbox) { if (! isContentManager(wpNodeRef)) { @@ -1095,63 +1068,52 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } WebProjectInfo wpInfo = getWebProject(wpNodeRef); - String wpStoreId = wpInfo.getStoreId(); - - boolean existingUser = false; - - // create a sandbox for the user with permissions based on role - // build a list of managers who will have full permissions on ALL staging areas - List managers = new ArrayList(4); + final String wpStoreId = wpInfo.getStoreId(); - // retrieve the list of managers from the existing users - Map existingUserRoles = listWebUsers(wpNodeRef); - for (Map.Entry userRole : existingUserRoles.entrySet()) - { - String username = userRole.getKey(); - String userrole = userRole.getValue(); - - if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) - { - managers.add(username); - } - - if (username.equals(userAuth)) - { - existingUser = true; - break; - } - } - - if (existingUser) + if (isWebUser(wpNodeRef, userAuth)) { logger.warn("User '"+userAuth+"' already invited to web project: "+wpNodeRef+" (store id: "+wpStoreId+")"); return; } else { + // build a list of managers who will have full permissions on ALL staging areas + List managers = new ArrayList(4); + + // retrieve the list of managers from the existing users + Map existingUserRoles = listWebUsers(wpNodeRef); + for (Map.Entry userRole : existingUserRoles.entrySet()) + { + String username = userRole.getKey(); + String userrole = userRole.getValue(); + + if (WCMUtil.ROLE_CONTENT_MANAGER.equals(userrole) && managers.contains(username) == false) + { + managers.add(username); + } + } + + if (autoCreateAuthorSandbox) + { + // create a sandbox for the user with permissions based on role + SandboxInfo sbInfo = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); + + List sandboxInfoList = new LinkedList(); + sandboxInfoList.add(sbInfo); + + // Bind the post-commit transaction listener with data required for virtualization server notification + CreateSandboxTransactionListener tl = new CreateSandboxTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); + AlfrescoTransactionSupport.bindListener(tl); + } + // if this new user is a manager, we'll need to update the manager permissions applied // to each existing user sandbox - to ensure that new user has access to them if (WCMUtil.ROLE_CONTENT_MANAGER.equals(role)) { managers.add(userAuth); - - // walk existing sandboxes and reapply manager permissions to include new manager user - for (Map.Entry userRole : existingUserRoles.entrySet()) - { - String username = userRole.getKey(); - sandboxFactory.updateSandboxManagers(wpStoreId, managers, username); - } - sandboxFactory.updateStagingAreaManagers(wpStoreId, wpNodeRef, managers); + sandboxFactory.updateSandboxManagers(wpStoreId, wpNodeRef, managers); } - // build the user sandboxes now we have the manager list - // and create an association to a node to represent invited user - - List sandboxInfoList = new LinkedList(); - - SandboxInfo info = sandboxFactory.createUserSandbox(wpStoreId, managers, userAuth, role); - sandboxInfoList.add(info); - sandboxFactory.addStagingAreaUser(wpStoreId, userAuth, role); // create an app:webuser instance for the user and assoc to the web project node @@ -1173,10 +1135,6 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } } - // Bind the post-commit transaction listener with data required for virtualization server notification - InviteWebUsersTransactionListener tl = new InviteWebUsersTransactionListener(sandboxInfoList, listWebApps(wpNodeRef)); - AlfrescoTransactionSupport.bindListener(tl); - if (logger.isInfoEnabled()) { logger.info("Invited web user: "+userAuth+" (store id: "+wpStoreId+")"); @@ -1202,13 +1160,21 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public void uninviteWebUser(String wpStoreId, String userAuth) { - uninviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth); + uninviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth, false); } /* (non-Javadoc) - * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(java.lang.String, java.lang.String, boolean) */ - public void uninviteWebUser(NodeRef wpNodeRef, String userAuth) + public void uninviteWebUser(String wpStoreId, String userAuth, boolean autoDeleteAuthorSandbox) + { + uninviteWebUser(findWebProjectNodeFromStore(wpStoreId), userAuth, autoDeleteAuthorSandbox); + } + + /* (non-Javadoc) + * @see org.alfresco.wcm.webproject.WebProjectService#uninviteWebUser(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, boolean) + */ + public void uninviteWebUser(NodeRef wpNodeRef, String userAuth, boolean autoDeleteAuthorSandbox) { if (! isContentManager(wpNodeRef)) { @@ -1222,8 +1188,13 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService String wpStoreId = wpInfo.getStoreId(); String userMainStore = WCMUtil.buildUserMainStoreName(wpStoreId, userAuth); - // remove the store reference from the website folder meta-data - List userInfoRefs = listWebUserRefs(wpNodeRef); + if (autoDeleteAuthorSandbox) + { + sandboxFactory.deleteSandbox(userMainStore); + } + + // remove the store reference from the website folder meta-data (see also WCMUtil.listWebUsers) + List userInfoRefs = nodeService.getChildAssocs(wpNodeRef, WCMAppModel.ASSOC_WEBUSER, RegexQNamePattern.MATCH_ALL); // retrieve the list of managers from the existing users List managers = new ArrayList(4); @@ -1246,57 +1217,18 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService if (userAuth.equals(user)) { - // found the sandbox to remove - String path = WCMUtil.buildSandboxRootPath(userMainStore); - - // Notify virtualisation server about removing this sandbox. - // - // Implementation note: - // - // Because the removal of virtual webapps in the - // virtualization server is recursive, it only - // needs to be given the name of the main store. - // - // This notification must occur *prior* to purging content - // within the AVM because the virtualization server must list - // the avm_webapps dir in each store to discover which - // virtual webapps must be unloaded. The virtualization - // server traverses the sandbox's stores in most-to-least - // dependent order, so clients don't have to worry about - // accessing a preview layer whose main layer has been torn - // out from under it. + // remove the association to this web project user meta-data + nodeService.removeChild(wpNodeRef, ref.getChildRef()); - WCMUtil.removeAllVServerWebapps(virtServerRegistry, path, true); + // remove permission for the user (also fixes ETWOONE-338) + permissionService.clearPermission(wpNodeRef, userAuth); - // TODO: Use the .sandbox-id. property to delete all sandboxes, - // rather than assume a sandbox always had a single preview - // layer attached. - - // purge the user main sandbox store from the system - avmService.purgeStore(userMainStore); - - // remove any locks this user may have - avmLockingService.removeStoreLocks(userMainStore); - - // purge the user preview sandbox store from the system - String userPreviewStore = WCMUtil.buildUserPreviewStoreName(wpStoreId, userAuth); - avmService.purgeStore(userPreviewStore); - - // remove any locks this user may have - avmLockingService.removeStoreLocks(userPreviewStore); - - // remove the association to this web project user meta-data - nodeService.removeChild(wpNodeRef, ref.getChildRef()); - - // remove permission for the user (also fixes ETWOONE-338 - also need to ensure that last content manager does not uninvite themselves) - permissionService.clearPermission(wpNodeRef, userAuth); - - if (logger.isInfoEnabled()) - { + if (logger.isInfoEnabled()) + { logger.info("Uninvited web user: "+userAuth+" (store id: "+wpStoreId+")"); - } + } - break; // for loop + break; // for loop } } } @@ -1375,14 +1307,14 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService } /** - * Invite Web Users Transaction listener - invoked after commit + * Create Sandbox Transaction listener - invoked after commit */ - private class InviteWebUsersTransactionListener extends TransactionListenerAdapter + private class CreateSandboxTransactionListener extends TransactionListenerAdapter { private List sandboxInfoList; private List webAppNames; - public InviteWebUsersTransactionListener(List sandboxInfoList, List webAppNames) + public CreateSandboxTransactionListener(List sandboxInfoList, List webAppNames) { this.sandboxInfoList = sandboxInfoList; this.webAppNames = webAppNames; diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java index 4e331d026b..96375c076e 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImplTest.java @@ -240,7 +240,31 @@ public class WebProjectServiceImplTest extends TestCase // Create a web project WebProjectInfo wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-create", TEST_WEBPROJ_NAME+"-create", TEST_TITLE, TEST_DESCRIPTION, TEST_DEFAULT_WEBAPP, TEST_USE_AS_TEMPLATE, null); checkWebProjectInfo(wpInfo, TEST_WEBPROJ_DNS+"-create", TEST_WEBPROJ_NAME+"-create", TEST_TITLE, TEST_DESCRIPTION, TEST_DEFAULT_WEBAPP, TEST_USE_AS_TEMPLATE); - + + // Duplicate web project dns/store name + try + { + // Try to create duplicate web project dns/store (-ve test) + wpService.createWebProject(TEST_WEBPROJ_DNS+"-create", TEST_WEBPROJ_NAME+"-x", TEST_TITLE+"x", TEST_DESCRIPTION+"x", TEST_DEFAULT_WEBAPP+"x", TEST_USE_AS_TEMPLATE, null); + fail("Shouldn't allow duplicate web project dns/store name"); + } + catch (AlfrescoRuntimeException exception) + { + // Expected + } + + // Duplicate web project folder/name + try + { + // Try to create duplicate web project folder/name (-ve test) + wpService.createWebProject(TEST_WEBPROJ_DNS+"x", TEST_WEBPROJ_NAME+"-create", TEST_TITLE+"x", TEST_DESCRIPTION+"x", TEST_DEFAULT_WEBAPP+"x", TEST_USE_AS_TEMPLATE, null); + fail("Shouldn't allow duplicate web project folder/name"); + } + catch (DuplicateChildNodeNameException exception) + { + // Expected + } + // Mangled case String dnsName = TEST_WEBPROJ_DNS+"some.unexpected.chars"; String name = dnsName + " name"; @@ -289,30 +313,6 @@ public class WebProjectServiceImplTest extends TestCase { // Expected } - - // Duplicate web project dns/store name - try - { - // Try to create duplicate web project dns/store (-ve test) - wpService.createWebProject(TEST_WEBPROJ_DNS+"-create", TEST_WEBPROJ_NAME+"-x", TEST_TITLE+"x", TEST_DESCRIPTION+"x", TEST_DEFAULT_WEBAPP+"x", TEST_USE_AS_TEMPLATE, null); - fail("Shouldn't allow duplicate web project dns/store name"); - } - catch (AlfrescoRuntimeException exception) - { - // Expected - } - - // Duplicate web project folder/name - try - { - // Try to create duplicate web project folder/name (-ve test) - wpService.createWebProject(TEST_WEBPROJ_DNS+"x", TEST_WEBPROJ_NAME+"-create", TEST_TITLE+"x", TEST_DESCRIPTION+"x", TEST_DEFAULT_WEBAPP+"x", TEST_USE_AS_TEMPLATE, null); - fail("Shouldn't allow duplicate web project folder/name"); - } - catch (DuplicateChildNodeNameException exception) - { - // Expected - } } private void checkWebProjectInfo(WebProjectInfo wpInfo, String expectedStoreId, String expectedName, String expectedTitle, @@ -525,7 +525,7 @@ public class WebProjectServiceImplTest extends TestCase wpInfo = wpService.createWebProject(TEST_WEBPROJ_DNS+"-delete2", TEST_WEBPROJ_NAME+"-delete2", TEST_TITLE, TEST_DESCRIPTION, TEST_DEFAULT_WEBAPP, true, null); assertNotNull(wpService.getWebProject(wpInfo.getStoreId())); - wpService.inviteWebUser(wpInfo.getNodeRef(), USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER); + wpService.inviteWebUser(wpInfo.getNodeRef(), USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, false); // Switch to USER_TWO AuthenticationUtil.setCurrentUser(USER_TWO); @@ -749,7 +749,7 @@ public class WebProjectServiceImplTest extends TestCase userGroupRoles.put(USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER); // Invite web users - test using wpNodeRef - wpService.inviteWebUsersGroups(wpNodeRef, userGroupRoles); + wpService.inviteWebUsersGroups(wpNodeRef, userGroupRoles, false); assertEquals(4, wpService.listWebUsers(wpNodeRef).size()); assertEquals(WCMUtil.ROLE_CONTENT_MANAGER, wpService.listWebUsers(wpNodeRef).get(USER_ADMIN)); @@ -763,11 +763,15 @@ public class WebProjectServiceImplTest extends TestCase webProjects = wpService.listWebProjects(); assertEquals(userOneWebProjectCount+1, webProjects.size()); + // Start: Test fix ETWOTWO-567 + // Test newly invited content manager can invite other userGroupRoles = new HashMap(); userGroupRoles.put(USER_FIVE, WCMUtil.ROLE_CONTENT_CONTRIBUTOR); - wpService.inviteWebUsersGroups(wpNodeRef, userGroupRoles); + wpService.inviteWebUsersGroups(wpNodeRef, userGroupRoles, false); + + // Finish: Test fix ETWOTWO-567 // Switch back to admin AuthenticationUtil.setCurrentUser(USER_ADMIN); @@ -818,7 +822,7 @@ public class WebProjectServiceImplTest extends TestCase wpService.inviteWebUser(wpInfo.getStoreId(), USER_ONE, WCMUtil.ROLE_CONTENT_PUBLISHER); // Invite one web user - test using wpNodeRef - wpService.inviteWebUser(wpNodeRef, USER_TWO, WCMUtil.ROLE_CONTENT_MANAGER); + wpService.inviteWebUser(wpNodeRef, USER_TWO, WCMUtil.ROLE_CONTENT_MANAGER, true); assertEquals(3, wpService.listWebUsers(wpNodeRef).size()); assertEquals(WCMUtil.ROLE_CONTENT_PUBLISHER, wpService.listWebUsers(wpNodeRef).get(USER_ONE)); @@ -831,8 +835,8 @@ public class WebProjectServiceImplTest extends TestCase assertEquals(1, wpService.listWebUsers(wpNodeRef2).size()); assertEquals(WCMUtil.ROLE_CONTENT_MANAGER, wpService.listWebUsers(wpNodeRef2).get(USER_ADMIN)); - wpService.inviteWebUser(wpNodeRef2, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER); - wpService.inviteWebUser(wpNodeRef2, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER); + wpService.inviteWebUser(wpNodeRef2, USER_TWO, WCMUtil.ROLE_CONTENT_PUBLISHER, false); + wpService.inviteWebUser(wpNodeRef2, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, false); assertEquals(3, wpService.listWebUsers(wpNodeRef2).size()); assertEquals(WCMUtil.ROLE_CONTENT_PUBLISHER, wpService.listWebUsers(wpNodeRef2).get(USER_TWO)); @@ -850,7 +854,7 @@ public class WebProjectServiceImplTest extends TestCase try { // Try to invite web user as a non-content-manager (-ve test) - wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER); + wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER, false); fail("Shouldn't be able to invite web user since not a content manager"); } catch (AccessDeniedException exception) @@ -864,7 +868,7 @@ public class WebProjectServiceImplTest extends TestCase try { // Try to invite web user as a non-content-manager (such as System) (-ve test) - wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER); + wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER, false); fail("Shouldn't be able to invite web user since not a content manager"); } catch (AccessDeniedException exception) @@ -878,7 +882,7 @@ public class WebProjectServiceImplTest extends TestCase AuthenticationUtil.setCurrentUser(USER_ONE); // Invite web user - wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER); + wpService.inviteWebUser(wpNodeRef2, USER_THREE, WCMUtil.ROLE_CONTENT_REVIEWER, false); // Switch back to admin AuthenticationUtil.setCurrentUser(USER_ADMIN); @@ -909,12 +913,12 @@ public class WebProjectServiceImplTest extends TestCase assertEquals(1, wpService.listWebUsers(wpNodeRef).size()); assertEquals(WCMUtil.ROLE_CONTENT_MANAGER, wpService.listWebUsers(wpNodeRef).get(USER_ADMIN)); - wpService.inviteWebUser(wpNodeRef, USER_FOUR, WCMUtil.ROLE_CONTENT_CONTRIBUTOR); + wpService.inviteWebUser(wpNodeRef, USER_FOUR, WCMUtil.ROLE_CONTENT_CONTRIBUTOR, false); assertEquals(2, wpService.listWebUsers(wpNodeRef).size()); assertEquals(WCMUtil.ROLE_CONTENT_CONTRIBUTOR, wpService.listWebUsers(wpNodeRef).get(USER_FOUR)); - wpService.inviteWebUser(wpNodeRef, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER); + wpService.inviteWebUser(wpNodeRef, USER_ONE, WCMUtil.ROLE_CONTENT_MANAGER, false); assertEquals(3, wpService.listWebUsers(wpNodeRef).size()); assertEquals(WCMUtil.ROLE_CONTENT_MANAGER, wpService.listWebUsers(wpNodeRef).get(USER_ONE)); @@ -931,7 +935,7 @@ public class WebProjectServiceImplTest extends TestCase try { // Try to uninvite web user as a non-content-manager (-ve test) - wpService.uninviteWebUser(wpNodeRef, USER_FOUR); + wpService.uninviteWebUser(wpNodeRef, USER_FOUR, false); fail("Shouldn't be able to uninvite web user since not a content manager"); } catch (AccessDeniedException exception) @@ -945,7 +949,7 @@ public class WebProjectServiceImplTest extends TestCase try { // Try to uninvite web user as a non-content-manager (such as System) (-ve test) - wpService.uninviteWebUser(wpNodeRef, USER_FOUR); + wpService.uninviteWebUser(wpNodeRef, USER_FOUR, false); fail("Shouldn't be able to uninvite web user since not a content manager"); } catch (AccessDeniedException exception) @@ -973,7 +977,7 @@ public class WebProjectServiceImplTest extends TestCase // Content manager can uninvite themself // Uninvite web user - test using wpNodeRef - wpService.uninviteWebUser(wpNodeRef, USER_ADMIN); + wpService.uninviteWebUser(wpNodeRef, USER_ADMIN, false); assertEquals(1, wpService.listWebUsers(wpNodeRef).size()); assertEquals(null, wpService.listWebUsers(wpNodeRef).get(USER_ADMIN)); @@ -985,7 +989,7 @@ public class WebProjectServiceImplTest extends TestCase assertEquals(WCMUtil.ROLE_CONTENT_MANAGER, wpService.listWebUsers(wpNodeRef).get(USER_ONE)); // Delete user (in this case, last invited content manager) - wpService.uninviteWebUser(wpNodeRef, USER_ONE); + wpService.uninviteWebUser(wpNodeRef, USER_ONE, false); try { @@ -1055,7 +1059,7 @@ public class WebProjectServiceImplTest extends TestCase { userRoles.put(TEST_USER+"-"+j, WCMUtil.ROLE_CONTENT_MANAGER); } - wpService.inviteWebUsersGroups(wpInfo.getNodeRef(), userRoles); + wpService.inviteWebUsersGroups(wpInfo.getNodeRef(), userRoles, false); } System.out.println("testPseudoScaleTest: invited "+SCALE_USERS+" content managers to each of "+SCALE_WEBPROJECTS+" web projects in "+(System.currentTimeMillis()-split)+" msecs"); @@ -1064,7 +1068,7 @@ public class WebProjectServiceImplTest extends TestCase for (int i = 1; i <= SCALE_USERS; i++) { - wpService.listWebProjects(TEST_USER+"-"+i); // ignore return + assertEquals(SCALE_WEBPROJECTS, wpService.listWebProjects(TEST_USER+"-"+i).size()); } System.out.println("testPseudoScaleTest: list web projects for "+SCALE_USERS+" content managers in "+(System.currentTimeMillis()-split)+" msecs"); diff --git a/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java b/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java new file mode 100644 index 0000000000..3361364b69 --- /dev/null +++ b/source/java/org/alfresco/wcm/webproject/script/ScriptWebProjectsTest.java @@ -0,0 +1,73 @@ +package org.alfresco.wcm.webproject.script; + +import java.util.HashMap; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.jscript.ClasspathScriptLocation; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.PropertyMap; + +public class ScriptWebProjectsTest extends BaseAlfrescoSpringTest { + + + private static final String USER_ONE = "WebProjectTestOne"; + private static final String USER_TWO = "WebProjectTestTwo"; + private static final String USER_THREE = "WebProjectTestThree"; + + private static final String URL_WEB_PROJECTS = "/api/wcm/webprojects"; + private AuthenticationService authenticationService; + private PersonService personService; + private ScriptService scriptService; + private AuthenticationComponent authenticationComponent; + + private void createUser(String userName) + { + if (this.authenticationService.authenticationExists(userName) == false) + { + this.authenticationService.createAuthentication(userName, "PWD".toCharArray()); + + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + this.personService.createPerson(ppOne); + } + } + + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); + + this.authenticationService = (AuthenticationService)this.applicationContext.getBean("AuthenticationService"); + this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); + this.personService = (PersonService)this.applicationContext.getBean("PersonService"); + + // Create users + createUser(USER_ONE); + createUser(USER_TWO); + createUser(USER_THREE); + + // Do tests as user one + this.authenticationComponent.setCurrentUser(USER_ONE); + + } + + public void testJSAPI() throws Exception + { + this.authenticationComponent.setCurrentUser("admin"); + + ScriptLocation location = new ClasspathScriptLocation("org/alfresco/wcm/webproject/script/test_WebProjectService.js"); + this.scriptService.executeScript(location, new HashMap(0)); + } + +} diff --git a/source/java/org/alfresco/wcm/webproject/script/WebProject.java b/source/java/org/alfresco/wcm/webproject/script/WebProject.java index a2ee2504e1..c3679e60ea 100644 --- a/source/java/org/alfresco/wcm/webproject/script/WebProject.java +++ b/source/java/org/alfresco/wcm/webproject/script/WebProject.java @@ -25,18 +25,21 @@ package org.alfresco.wcm.webproject.script; import java.io.Serializable; +import java.util.List; import java.util.Map; +import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.repo.jscript.ScriptableHashMap; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.wcm.webproject.WebProjectInfo; import org.alfresco.wcm.webproject.WebProjectService; - -import com.sun.corba.se.spi.orbutil.fsm.Guard.Result; +import org.alfresco.wcm.sandbox.SandboxInfo; +import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.sandbox.script.Sandbox; /** * WebProject object to expose via JavaScript - * */ public class WebProject implements Serializable { @@ -49,8 +52,6 @@ public class WebProject implements Serializable * */ private static final long serialVersionUID = -2194205151549790079L; - - WebProjectService service; WebProjectInfo info; @@ -59,11 +60,12 @@ public class WebProject implements Serializable private String description; private boolean isTemplate; private String webProjectRef; + private WebProjects webprojects; /* * Constructor for Outbound WebProjects */ - public WebProject(WebProjectInfo info, WebProjectService service) + public WebProject(WebProjects webprojects, WebProjectInfo info) { this.info = info; this.name = info.getName(); @@ -71,7 +73,7 @@ public class WebProject implements Serializable this.description = info.getDescription(); this.isTemplate = info.isTemplate(); this.webProjectRef = info.getStoreId(); - this.service = service; + this.webprojects = webprojects; } public void setName(String name) { @@ -123,16 +125,23 @@ public class WebProject implements Serializable } // - public String getWebProjectRef() { + public String getWebProjectRef() + { return webProjectRef; } + // read-only property + public NodeRef getNodeRef() + { + return info.getNodeRef(); + } + /** * delete this web project */ public void deleteWebProject() { - service.deleteWebProject(webProjectRef); + getWebProjectService().deleteWebProject(webProjectRef); } /** @@ -140,7 +149,74 @@ public class WebProject implements Serializable */ public void save() { - service.updateWebProject(info); + getWebProjectService().updateWebProject(info); + } + + /** + * getSandboxes + * @param userName + * @return the sandboxes or an empty map if there are none. + */ + public ScriptableHashMap getSandboxes(String userName) + { + ScriptableHashMap result = new ScriptableHashMap(); + + // TODO at the moment the user can only have one sandbox - this will change in future + SandboxInfo si = getSandboxService().getAuthorSandbox(webProjectRef, userName); + if(si != null) + { + Sandbox sandbox = new Sandbox(this, si); + result.put(userName, sandbox); + } + return result; + } + + + /** + * Create a user sandbox, if the user already has a sandbox does nothing. + * @param userName + * @return the newly created sandbox details + */ + public Sandbox createSandbox(String userName) + { + SandboxInfo si = getSandboxService().createAuthorSandbox(webProjectRef, userName); + Sandbox sandbox = new Sandbox(this, si); + return sandbox; + } + + /** + * Get a single sandbox by its unique reference + * @param sandboxRef + * @return the sandbox or null if it is not found. + */ + public Sandbox getSandbox(String sandboxRef) + { + SandboxInfo si = getSandboxService().getSandbox(sandboxRef); + if(si != null) + { + Sandbox sandbox = new Sandbox(this, si); + return sandbox; + } + return null; + } + + /** + * getSandboxes for this web project + * @return the sandboxes + */ + public ScriptableHashMap getSandboxes() + { + List si = getSandboxService().listSandboxes(webProjectRef); + + ScriptableHashMap result = new ScriptableHashMap(); + + for(SandboxInfo s : si) + { + Sandbox b = new Sandbox(this, s); + result.put(b.getSandboxRef(), b); + } + + return result; } /** @@ -153,7 +229,7 @@ public class WebProject implements Serializable */ public String getMembersRole(String userName) { - return service.getWebUserRole(webProjectRef, userName); + return getWebProjectService().getWebUserRole(webProjectRef, userName); } /** @@ -170,7 +246,7 @@ public class WebProject implements Serializable */ public void addMembership(String userName, String role) { - service.inviteWebUser(webProjectRef, userName, role); + getWebProjectService().inviteWebUser(webProjectRef, userName, role); } /** @@ -183,7 +259,7 @@ public class WebProject implements Serializable */ public void removeMembership(String userName) { - service.uninviteWebUser(webProjectRef, userName); + getWebProjectService().uninviteWebUser(webProjectRef, userName); } /** @@ -193,7 +269,7 @@ public class WebProject implements Serializable */ public ScriptableHashMap listMembers() { - Map members = service.listWebUsers(webProjectRef); + Map members = getWebProjectService().listWebUsers(webProjectRef); ScriptableHashMap result = new ScriptableHashMap(); result.putAll(members); @@ -203,12 +279,36 @@ public class WebProject implements Serializable /** * List the role (name) for a WCM project - * @return the roles for a WCM project + * @return a map of roles for a WCM project (value, name) */ public ScriptableHashMap getRoles() { - // TODO Not yet implemented. + //TODO Role names should be I811N from webclient.properties + //ContentManager=Content Manager + //ContentPublisher=Content Publisher + //ContentContributor=Content Contributor + //ContentReviewer=Content Reviewer ScriptableHashMap result = new ScriptableHashMap(); + result.put(ROLE_CONTENT_MANAGER, "Content Manager"); + result.put(ROLE_CONTENT_PUBLISHER, "Content Publisher"); + result.put(ROLE_CONTENT_REVIEWER, "Content Reviewer"); + result.put(ROLE_CONTENT_CONTRIBUTOR, "Content Contributor"); + return result; } + + public WebProjects getWebProjects() + { + return this.webprojects; + } + + public SandboxService getSandboxService() + { + return getWebProjects().getSandboxService(); + } + + public WebProjectService getWebProjectService() + { + return getWebProjects().getWebProjectService(); + } } diff --git a/source/java/org/alfresco/wcm/webproject/script/WebProjects.java b/source/java/org/alfresco/wcm/webproject/script/WebProjects.java index e9242d34d0..09674f6fb5 100644 --- a/source/java/org/alfresco/wcm/webproject/script/WebProjects.java +++ b/source/java/org/alfresco/wcm/webproject/script/WebProjects.java @@ -30,6 +30,7 @@ import org.alfresco.repo.jscript.BaseScopableProcessorExtension; import org.alfresco.repo.model.Repository; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.wcm.sandbox.SandboxService; import org.alfresco.wcm.webproject.WebProjectInfo; import org.alfresco.wcm.webproject.WebProjectService; @@ -44,9 +45,12 @@ public class WebProjects extends BaseScopableProcessorExtension /** Service Registry */ private ServiceRegistry serviceRegistry; - /** The site service */ + /** The web projects service */ private WebProjectService webProjectService; + /** The sandbox service */ + private SandboxService sandboxService; + /** * Sets the Service Registry * @@ -67,6 +71,26 @@ public class WebProjects extends BaseScopableProcessorExtension this.webProjectService = webProjectService; } + public WebProjectService getWebProjectService() + { + return this.webProjectService; + } + + /** + * Set the wcm sandbox service + * + * @param webProjectService the wcm web project service + */ + public void setSandboxService(SandboxService sandboxService) + { + this.sandboxService = sandboxService; + } + + public SandboxService getSandboxService() + { + return this.sandboxService; + } + /** * create web project * @param name @@ -77,7 +101,7 @@ public class WebProjects extends BaseScopableProcessorExtension public WebProject createWebProject(String dnsName, String name, String title, String description ) { WebProjectInfo info = webProjectService.createWebProject(dnsName, name, title, description); - return new WebProject(info, webProjectService); + return new WebProject(this, info); } /** @@ -90,7 +114,7 @@ public class WebProjects extends BaseScopableProcessorExtension WebProjectInfo info = webProjectService.getWebProject(webProjectRef); if(info != null){ - WebProject retVal = new WebProject(info, webProjectService); + WebProject retVal = new WebProject(this, info); return retVal; } return null; @@ -109,7 +133,7 @@ public class WebProjects extends BaseScopableProcessorExtension int i= 0; for(WebProjectInfo info : projects) { - ret[i++] = new WebProject(info, webProjectService); + ret[i++] = new WebProject(this, info); } return ret; } @@ -127,7 +151,7 @@ public class WebProjects extends BaseScopableProcessorExtension int i= 0; for(WebProjectInfo info : projects) { - ret[i++] = new WebProject(info, webProjectService); + ret[i++] = new WebProject(this, info); } return ret; } diff --git a/source/java/org/alfresco/wcm/webproject/script/test_WebProjectService.js b/source/java/org/alfresco/wcm/webproject/script/test_WebProjectService.js new file mode 100644 index 0000000000..b62c84c1f6 --- /dev/null +++ b/source/java/org/alfresco/wcm/webproject/script/test_WebProjectService.js @@ -0,0 +1,45 @@ +/** + * Test WCM Web Project Java Script Interface + */ + +function testListWebProjects() +{ + var service = webprojects; + test.assertNotNull(service, "Service is null."); + + var list1 = service.listWebProjects(); + var length = list1.length; + + var newProjA = service.createWebProject("TestA", "test a website", "description", "jsTestA"); + var newProjB = service.createWebProject("TestB", "test b website", "description", "jsTestB"); + var newProjC = service.createWebProject("TestC", "test c website", "description", "jsTestC"); + + var list2 = service.listWebProjects(); + test.assertNotNull(list2, "list2 is null."); + test.assertTrue(list2.length >= 3 + length, "list too small"); + + newProjA.deleteWebProject(); + newProjB.deleteWebProject(); + newProjC.deleteWebProject(); + +} + +function testCRUD() +{ + var service = webprojects; + test.assertNotNull(service, "Service is null."); + + // Try and get a web project that doesn't exist. + var newProj = service.createWebProject("name", "title", "description", "jsTest"); + + var node = newProj.getNodeRef(); + + test.assertNotNull(node.id, "node.id is null."); + + newProj.deleteWebProject(); +} + + +// Execute test's +testCRUD(); +testListWebProjects();