diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 6e8a3d37a7..e7fa2ec5ae 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -302,6 +302,7 @@ alfresco/workflow/wcm-workflow-messages + diff --git a/config/alfresco/extension/mt/mt-admin-context.xml.sample b/config/alfresco/extension/mt/mt-admin-context.xml.sample index 41e5e34a90..c31ff643fc 100644 --- a/config/alfresco/extension/mt/mt-admin-context.xml.sample +++ b/config/alfresco/extension/mt/mt-admin-context.xml.sample @@ -24,10 +24,7 @@ - - - - + diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 2a195a15da..5e4d964910 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -14,7 +14,10 @@ ${server.transaction.allow-writes} - + + + + @@ -109,8 +112,8 @@ - - + + @@ -120,9 +123,8 @@ - - + diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 506dd9dcda..77fa99a841 100755 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -25,7 +25,6 @@ package org.alfresco.repo.tenant; import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -46,30 +45,24 @@ import org.alfresco.repo.attributes.MapAttributeValue; import org.alfresco.repo.attributes.StringAttributeValue; import org.alfresco.repo.content.TenantRoutingFileContentStore; import org.alfresco.repo.dictionary.DictionaryComponent; -import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.workflow.WorkflowDefinitionType; import org.alfresco.repo.workflow.WorkflowDeployer; import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.view.RepositoryExporterService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.AbstractLifecycleBean; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; -import org.springframework.core.io.ClassPathResource; /** * MT Admin Service Implementation. @@ -93,10 +86,7 @@ public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements Ten private TenantRoutingFileContentStore tenantFileContentStore; private WorkflowService workflowService; private RepositoryExporterService repositoryExporterService; - private NamespaceService namespaceService; - private SearchService searchService; - private RepositoryLocation repoWorkflowDefsLocation; - private WorkflowDefinitionType workflowDefinitionType; + private WorkflowDeployer workflowDeployer; protected final static String REGEX_VALID_TENANT_NAME = "^[a-zA-Z0-9]([a-zA-Z0-9]|.[a-zA-Z0-9])*$"; // note: must also be a valid filename @@ -156,24 +146,9 @@ public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements Ten this.repositoryExporterService = repositoryExporterService; } - public void setNamespaceService(NamespaceService namespaceService) + public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) { - this.namespaceService = namespaceService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setRepositoryWorkflowDefsLocations(RepositoryLocation repoWorkflowDefsLocation) - { - this.repoWorkflowDefsLocation = repoWorkflowDefsLocation; - } - - public void setWorkflowDefinitionType(WorkflowDefinitionType workflowDefinitionType) - { - this.workflowDefinitionType = workflowDefinitionType; + this.workflowDeployer = workflowDeployer; } @@ -192,9 +167,6 @@ public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements Ten private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; private static final String ADMIN_BASENAME = TenantService.ADMIN_BASENAME; - - public final static String CRITERIA_ALL = "/*"; // immediate children only - public final static String defaultSubtypeOfWorkflowDefinitionType = "subtypeOf('bpm:workflowDefinition')"; private List tenantDeployers = new ArrayList(); @@ -577,48 +549,18 @@ public class MultiTAdminServiceImpl extends AbstractLifecycleBean implements Ten return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); } - public void bootstrapWorkflows() + public void bootstrapWorkflows(String tenantDomain) { - // use this to deploy standard workflow process defs to the JBPM engine - WorkflowDeployer workflowBootstrap = (WorkflowDeployer)getApplicationContext().getBean("workflowBootstrap"); - - String resourceClasspath = null; - - // Workflow process definitions - try - { - List workflowDefs = workflowBootstrap.getWorkflowDefinitions(); - if (workflowDefs != null) - { - for (Properties workflowDefProps : workflowDefs) + AuthenticationUtil.runAs(new RunAsWork() { - resourceClasspath = workflowDefProps.getProperty(WorkflowDeployer.LOCATION); - ClassPathResource resource = new ClassPathResource(resourceClasspath); - workflowService.deployDefinition(workflowDefProps.getProperty(WorkflowDeployer.ENGINE_ID), resource.getInputStream(), workflowDefProps.getProperty(WorkflowDeployer.MIMETYPE)); - } - } - } - catch (IOException ioe) - { - throw new AlfrescoRuntimeException("Failed to find workflow process def: " + resourceClasspath); - } + public Object doWork() + { + workflowDeployer.init(); + return null; + } + }, getTenantAdminUser(tenantDomain)); - // in case of import, also deploy any custom workflow definitions defined in the repo - // TODO refactor/review repository bootstrap order of undeployed workflow definitions - - StoreRef storeRef = repoWorkflowDefsLocation.getStoreRef(); - NodeRef rootNode = nodeService.getRootNode(storeRef); - List nodeRefs = searchService.selectNodes(rootNode, repoWorkflowDefsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfWorkflowDefinitionType+"]", null, namespaceService, false); - - if (nodeRefs.size() > 0) - { - for (NodeRef nodeRef : nodeRefs) - { - workflowDefinitionType.deploy(nodeRef); - } - } - - logger.info("Tenant workflows bootstrapped: " + tenantService.getCurrentUserDomain()); + logger.info("Tenant workflows bootstrapped: " + tenantDomain); } /** diff --git a/source/java/org/alfresco/repo/tenant/TenantAdminService.java b/source/java/org/alfresco/repo/tenant/TenantAdminService.java index 55c449c1d2..62a8a04bb8 100755 --- a/source/java/org/alfresco/repo/tenant/TenantAdminService.java +++ b/source/java/org/alfresco/repo/tenant/TenantAdminService.java @@ -47,7 +47,7 @@ public interface TenantAdminService extends TenantDeployerService public boolean existsTenant(String tenantDomain); - public void bootstrapWorkflows(); + public void bootstrapWorkflows(String tenantDomain); public void deleteTenant(String tenantDomain); diff --git a/source/java/org/alfresco/repo/tenant/TenantInterpreter.java b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java index 8367779b92..c348aa0d54 100755 --- a/source/java/org/alfresco/repo/tenant/TenantInterpreter.java +++ b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java @@ -239,16 +239,8 @@ public class TenantInterpreter extends BaseInterpreter } String newTenant = new String(command[1]).toLowerCase(); - - String tenantAdminUsername = tenantService.getDomainUser(TenantService.ADMIN_BASENAME, newTenant); - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - tenantAdminService.bootstrapWorkflows(); - return null; - } - }, tenantAdminUsername); + + tenantAdminService.bootstrapWorkflows(newTenant); out.println("bootstrap workflows deployed for tenant: " + newTenant); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java index b3cd6b1c2b..0910478d36 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionType.java @@ -25,23 +25,15 @@ package org.alfresco.repo.workflow; import java.io.Serializable; -import java.util.List; import java.util.Map; -import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowDeployment; -import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * Workflow Definition type behaviour. @@ -52,29 +44,11 @@ public class WorkflowDefinitionType implements ContentServicePolicies.OnContentU NodeServicePolicies.OnUpdatePropertiesPolicy, NodeServicePolicies.BeforeDeleteNodePolicy { - // logger - private static Log logger = LogFactory.getLog(WorkflowDefinitionType.class); - - /** The node service */ - private NodeService nodeService; - /** The policy component */ private PolicyComponent policyComponent; - - /** The workflow service */ - private WorkflowService workflowService; - - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } + /** The workflow deployer / undeployer */ + private WorkflowDeployer workflowDeployer; /** * Set the policy component @@ -87,16 +61,15 @@ public class WorkflowDefinitionType implements ContentServicePolicies.OnContentU } /** - * Set the workflow service + * Set the workflow deployer / undeployer * - * @param workflowService the workflow service + * @param workflowDeployer the workflow deployer / undeployer */ - public void setWorkflowService(WorkflowService workflowService) + public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) { - this.workflowService = workflowService; + this.workflowDeployer = workflowDeployer; } - /** * The initialise method */ @@ -128,7 +101,7 @@ public class WorkflowDefinitionType implements ContentServicePolicies.OnContentU */ public void onContentUpdate(NodeRef nodeRef, boolean newContent) { - deploy(nodeRef); + workflowDeployer.deploy(nodeRef, true); } /** @@ -151,78 +124,23 @@ public class WorkflowDefinitionType implements ContentServicePolicies.OnContentU { if (afterValue.booleanValue() == true) { - deploy(nodeRef); + workflowDeployer.deploy(nodeRef, true); } else { - undeploy(nodeRef); + workflowDeployer.undeploy(nodeRef); } } else if (afterValue == null && beforeValue != null) { // Undeploy the definition since the value has been cleared - undeploy(nodeRef); + workflowDeployer.undeploy(nodeRef); } } public void beforeDeleteNode(NodeRef nodeRef) { - undeploy(nodeRef); - } - - public void deploy(NodeRef nodeRef) - { - // Ignore if the node is a working copy - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) - { - Boolean value = (Boolean)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEF_DEPLOYED); - if ((value != null) && (value.booleanValue() == true)) - { - // deploy / re-deploy - WorkflowDeployment deployment = workflowService.deployDefinition(nodeRef); - - if (deployment != null) - { - WorkflowDefinition def = deployment.definition; - - // Update the meta data for the model - Map props = nodeService.getProperties(nodeRef); - - props.put(WorkflowModel.PROP_WORKFLOW_DEF_NAME, def.getName()); - - // TODO - ability to return and handle deployment problems / warnings - if (deployment.problems.length > 0) - { - for (String problem : deployment.problems) - { - logger.warn(problem); - } - } - - nodeService.setProperties(nodeRef, props); - } - } - } - } - - private void undeploy(NodeRef nodeRef) - { - // Ignore if the node is a working copy - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) - { - String defName = (String)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEF_NAME); - if (defName != null) - { - // Undeploy the workflow definition - all versions in JBPM - List defs = workflowService.getAllDefinitionsByName(defName); - for (WorkflowDefinition def: defs) - { - logger.info("Undeploying workflow '" + defName + "' ..."); - workflowService.undeployDefinition(def.getId()); - logger.info("... undeployed '" + def.getId() + "' v" + def.getVersion()); - } - } - } + workflowDeployer.undeploy(nodeRef); } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 4c9dcd8cf3..8e4289e77d 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -24,23 +24,34 @@ */ package org.alfresco.repo.workflow; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Properties; import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; import org.alfresco.repo.dictionary.DictionaryBootstrap; import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.view.ImporterException; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; @@ -75,7 +86,14 @@ public class WorkflowDeployer extends AbstractLifecycleBean private List models = new ArrayList(); private List resourceBundles = new ArrayList(); private TenantService tenantService; + + private NodeService nodeService; + private NamespaceService namespaceService; + private SearchService searchService; + private RepositoryLocation repoWorkflowDefsLocation; + public final static String CRITERIA_ALL = "/*"; // immediate children only + public final static String defaultSubtypeOfWorkflowDefinitionType = "subtypeOf('bpm:workflowDefinition')"; /** * Set whether we write or not @@ -172,6 +190,28 @@ public class WorkflowDeployer extends AbstractLifecycleBean { return this.workflowDefinitions; } + + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setRepositoryWorkflowDefsLocations(RepositoryLocation repoWorkflowDefsLocation) + { + this.repoWorkflowDefsLocation = repoWorkflowDefsLocation; + } + /** * Deploy the Workflow Definitions @@ -258,6 +298,22 @@ public class WorkflowDeployer extends AbstractLifecycleBean } } } + + // deploy workflow definitions from repository (if any) + if (repoWorkflowDefsLocation != null) + { + StoreRef storeRef = repoWorkflowDefsLocation.getStoreRef(); + NodeRef rootNode = nodeService.getRootNode(storeRef); + List nodeRefs = searchService.selectNodes(rootNode, repoWorkflowDefsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfWorkflowDefinitionType+"]", null, namespaceService, false); + + if (nodeRefs.size() > 0) + { + for (NodeRef nodeRef : nodeRefs) + { + deploy(nodeRef, false); + } + } + } userTransaction.commit(); } @@ -276,6 +332,94 @@ public class WorkflowDeployer extends AbstractLifecycleBean } } + public void deploy(NodeRef nodeRef, boolean redeploy) + { + // Ignore if the node is a working copy + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) + { + Boolean value = (Boolean)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEF_DEPLOYED); + if ((value != null) && (value.booleanValue() == true)) + { + if (!redeploy && workflowService.isDefinitionDeployed(nodeRef)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Workflow deployer: Definition '" + nodeRef + "' already deployed"); + } + } + else + { + // deploy / re-deploy + WorkflowDeployment deployment = workflowService.deployDefinition(nodeRef); + if (logger.isInfoEnabled()) + { + logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + nodeRef + "' with " + deployment.problems.length + " problems"); + } + if (deployment != null) + { + WorkflowDefinition def = deployment.definition; + + // Update the meta data for the model + Map props = nodeService.getProperties(nodeRef); + + props.put(WorkflowModel.PROP_WORKFLOW_DEF_NAME, def.getName()); + + // TODO - ability to return and handle deployment problems / warnings + if (deployment.problems.length > 0) + { + for (String problem : deployment.problems) + { + logger.warn(problem); + } + } + + nodeService.setProperties(nodeRef, props); + } + } + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Workflow deployer: Definition '" + nodeRef + "' not deployed since it is a working copy"); + } + } + } + + public void undeploy(NodeRef nodeRef) + { + // Ignore if the node is a working copy + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) + { + String defName = (String)nodeService.getProperty(nodeRef, WorkflowModel.PROP_WORKFLOW_DEF_NAME); + if (defName != null) + { + // Undeploy the workflow definition - all versions in JBPM + List defs = workflowService.getAllDefinitionsByName(defName); + for (WorkflowDefinition def: defs) + { + if (logger.isInfoEnabled()) + { + logger.info("Undeploying workflow '" + defName + "' ..."); + } + workflowService.undeployDefinition(def.getId()); + if (logger.isInfoEnabled()) + { + logger.info("... undeployed '" + def.getId() + "' v" + def.getVersion()); + } + } + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Workflow deployer: Definition '" + nodeRef + "' not undeployed since it is a working copy"); + } + } + } + @Override protected void onBootstrap(ApplicationEvent event) { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 33678171a1..aae6ec0334 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -143,7 +143,23 @@ public class WorkflowServiceImpl implements WorkflowService return deployment; } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#isDefinitionDeployed(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isDefinitionDeployed(NodeRef workflowDefinition) + { + if (! nodeService.getType(workflowDefinition).equals(WorkflowModel.TYPE_WORKFLOW_DEF)) + { + throw new WorkflowException("Node " + workflowDefinition + " is not of type 'bpm:workflowDefinition'"); + } + + String engineId = (String)nodeService.getProperty(workflowDefinition, WorkflowModel.PROP_WORKFLOW_DEF_ENGINE_ID); + ContentReader contentReader = contentService.getReader(workflowDefinition, ContentModel.PROP_CONTENT); + return isDefinitionDeployed(engineId, contentReader.getContentInputStream(), contentReader.getMimetype()); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#isDefinitionDeployed(java.lang.String, java.io.InputStream, java.lang.String) */ diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index cf57848682..482726f77e 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -72,6 +72,18 @@ public interface WorkflowService @Auditable(key = Auditable.Key.ARG_0, parameters = {"workflowDefinition"}) public WorkflowDeployment deployDefinition(NodeRef workflowDefinition); + /** + * Is the specified Workflow Definition already deployed? + * + * Note: the notion of "already deployed" may differ between bpm engines. For example, + * different versions of the same process may be considered equal. + * + * @param workflowDefinition the content object containing the definition + * @return true => already deployed + */ + @Auditable(parameters = {"definitionContent"}) + public boolean isDefinitionDeployed(NodeRef workflowDefinition); + /** * Is the specified Workflow Definition already deployed? *