From 411dccf795241c7651c0cd15e813dea200e04ab4 Mon Sep 17 00:00:00 2001 From: N Smith Date: Tue, 19 Apr 2011 13:26:38 +0000 Subject: [PATCH] ALF-8144 Added in caching of QName mappings, TaskInstances, Task properties and ProcessContext variables. Effectively merged fixes from V3.4 revisions 25061,25072,25261 and 25326. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@26973 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/hibernate-context.xml | 2 + .../repo/workflow/AlfrescoBpmEngine.java | 3 +- .../repo/workflow/WorkflowObjectFactory.java | 5 + .../repo/workflow/WorkflowQNameConverter.java | 52 ++- .../repo/workflow/jbpm/JBPMEngine.java | 435 +++++++++++++----- .../workflow/jbpm/JBPMJunit4LoadTests.java | 288 ++++++++++++ 6 files changed, 659 insertions(+), 126 deletions(-) create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 1ab65634dc..6b7f56290c 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -135,6 +135,8 @@ org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml org/jbpm/job/CleanUpProcessJob.hbm.xml + org/alfresco/repo/workflow/jbpm/jbpm.ext.queries.hbm.xml + diff --git a/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java b/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java index b349c62756..ef81f122a1 100644 --- a/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java +++ b/source/java/org/alfresco/repo/workflow/AlfrescoBpmEngine.java @@ -24,6 +24,7 @@ import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QNameCache; /** * @since 3.4.e @@ -38,7 +39,7 @@ public abstract class AlfrescoBpmEngine extends BPMEngine protected DictionaryService dictionaryService; protected WorkflowObjectFactory factory; protected WorkflowAuthorityManager authorityManager; - + /** * {@inheritDoc} */ diff --git a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java index dbd1d855dc..0f133843a1 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java @@ -381,4 +381,9 @@ public class WorkflowObjectFactory { return qNameConverter.mapNameToQName(name); } + + public void clearQNameCache() + { + qNameConverter.clearCache(); + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java b/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java index 291f5fe71e..47cabeaf3e 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowQNameConverter.java @@ -22,6 +22,7 @@ package org.alfresco.repo.workflow; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.QNameCache; /** * @since 3.4.e @@ -30,6 +31,8 @@ import org.alfresco.service.namespace.QName; */ public class WorkflowQNameConverter { + private static final int MAX_QNAME_CACHE_SIZE = 5000; + private final QNameCache cache = new QNameCache(MAX_QNAME_CACHE_SIZE); private final NamespacePrefixResolver prefixResolver; public WorkflowQNameConverter(NamespacePrefixResolver prefixResolver) @@ -40,21 +43,21 @@ public class WorkflowQNameConverter /** * Map QName to jBPM variable name * - * @param name QName + * @param qName QName * @return jBPM variable name */ - public String mapQNameToName(QName name) + public String mapQNameToName(QName qName) { - // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) - // NOTE: Use new scheme - String nameStr = name.toPrefixString(prefixResolver); - if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) + String name = cache.getName(qName); + if(name == null) { - return nameStr.replace(':', '}'); + name = convertQNameToName(qName); + cache.putQNameToName(qName, name); + cache.putNameToQName(name, qName); } - return nameStr.replace(':', '_'); + return name; } - + /** * Map QName to jBPM variable name * @@ -62,6 +65,23 @@ public class WorkflowQNameConverter * @return jBPM variable name */ public QName mapNameToQName(String name) + { + QName qName = cache.getQName(name); + if (qName == null) + { + qName = convertNameToQName(name); + cache.putNameToQName(name, qName); + cache.putQNameToName(qName, name); + } + return qName; + } + + public void clearCache() + { + cache.clear(); + } + + private QName convertNameToQName(String name) { if(name.indexOf(QName.NAMESPACE_BEGIN)==0) { @@ -78,5 +98,19 @@ public class WorkflowQNameConverter } return QName.createQName(qName, prefixResolver); } + + private String convertQNameToName(QName name) + { + // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) + // NOTE: Use new scheme + String nameStr = name.toPrefixString(prefixResolver); + if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) + { + // Return full QName string. + return name.toString(); + } + // Return prefixed QName string. + return nameStr.replace(':', '_'); + } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index dbeb942f30..4f62d68ecf 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -66,11 +66,12 @@ import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.cmr.workflow.WorkflowTimer; import org.alfresco.service.cmr.workflow.WorkflowTransition; -import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; +import org.hibernate.CacheMode; import org.hibernate.Criteria; +import org.hibernate.FlushMode; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.criterion.Conjunction; @@ -125,7 +126,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine protected AuthorityDAO authorityDAO; protected JbpmTemplate jbpmTemplate; protected SearchService unprotectedSearchService; - + // Company Home protected StoreRef companyHomeStore; protected String companyHomePath; @@ -738,7 +739,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine Serializable packageNode = parameters.get(WorkflowModel.ASSOC_PACKAGE); if (packageNode != null) { - String pckgName = mapQNameToName(WorkflowModel.ASSOC_PACKAGE); + String pckgName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); processContext.setVariable(pckgName, new JBPMNode((NodeRef) packageNode, serviceRegistry)); } } @@ -793,7 +794,6 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java.lang.String) */ - @SuppressWarnings("unchecked") public List getActiveWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, true); @@ -992,7 +992,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine for (Map.Entry entry : tokenVars.entrySet()) { String key = entry.getKey(); - QName qname = mapNameToQName(key); + QName qname = factory.mapNameToQName(key); if (!properties.containsKey(key)) { @@ -1312,46 +1312,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine List tasks; if (state.equals(WorkflowTaskState.IN_PROGRESS)) { - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - tasks = taskSession.findTaskInstances(authority); + return findActiveTaskInstances(authority, context); } else { // Note: This method is not implemented by jBPM tasks = findCompletedTaskInstances(context, authority); + return getWorkflowTasks(tasks); } - return getWorkflowTasks(tasks); - } - - /** - * Gets the completed task list for the specified actor - * - * TODO: This method provides a query that's not in JBPM! Look - * to have JBPM implement this. - * - * @param jbpmContext - * the jbpm context - * @param actorId - * the actor to retrieve tasks for - * @return the tasks - */ - private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) - { - List result = null; - try - { - Session session = jbpmContext.getSession(); - Query query = session.createQuery(COMPLETED_TASKS_QUERY); - query.setString("actorId", actorId); - result = query.list(); - } - catch (Exception e) - { - String msg = messageService.getMessage(ERR_FIND_COMPLETED_TASK_INSTS, actorId); - throw new JbpmException(msg, e); - } - return result; } }); } @@ -1362,8 +1331,207 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine } } - /* (non-Javadoc) - * @see org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) + /** + * Gets the completed task list for the specified actor + * + * @param jbpmContext the jbpm context + * @param actorId the actor to retrieve tasks for + * @return the tasks + */ + @SuppressWarnings("unchecked") + private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) + { + List result = null; + try + { + Session session = jbpmContext.getSession(); + Query query = session.createQuery(COMPLETED_TASKS_QUERY); + query.setString("actorId", actorId); + result = query.list(); + } + catch (Exception e) + { + String msg = messageService.getMessage(ERR_FIND_COMPLETED_TASK_INSTS, actorId); + throw new JbpmException(msg, e); + } + return result; + } + + @SuppressWarnings("unchecked") + private List findActiveTaskInstances(final String authority, JbpmContext context) + { + Session session = context.getSession(); + Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); + query.setString("actorId", authority); + query.setBoolean("true", true); + List workflowTasks = getWorkflowTasks(session, query.list()); + // Do we need to clear a session here? It takes 3 seconds with 2000 workflows. + // session.clear(); + return workflowTasks; + } + + protected List getWorkflowTasks(Session session, List rows) + { + List workflowTasks = new ArrayList(rows.size()); + + /// ------------------------ + + // Preload data into L1 session + List taskInstanceIds = new ArrayList(rows.size()); + List contextInstanceIds = new ArrayList(rows.size()); + for (Object[] row : rows) + { + TaskInstance ti = (TaskInstance) row[0]; + taskInstanceIds.add(ti.getId()); + ContextInstance ci = (ContextInstance) row[8]; + contextInstanceIds.add(ci.getId()); + } + Map taskInstanceCache = new HashMap(rows.size()); + if (taskInstanceIds.size() > 0) + { + taskInstanceCache = cacheTasks(session, taskInstanceIds); + } + Map variablesCache = new HashMap(rows.size()); + if (contextInstanceIds.size() > 0) + { + variablesCache = cacheVariables(session, contextInstanceIds); + } + taskInstanceIds.clear(); + contextInstanceIds.clear(); + /// ------------------------ + for(Object[] row : rows) + { + WorkflowTask workflowTask = makeWorkflowTask(row, taskInstanceCache, variablesCache); + if(workflowTask !=null ) + { + workflowTasks.add(workflowTask); + } + } + return workflowTasks; + } + + private WorkflowTask makeWorkflowTask(Object[] row, Map taskInstanceCache, Map variablesCache) + { + TaskInstance ti = (TaskInstance) row[0]; + Token token = (Token)row[2]; + ProcessInstance processInstance = (ProcessInstance)row[3]; + Node node = (Node)row[4]; + Task task = (Task)row[5]; + ProcessDefinition processDefinition = (ProcessDefinition)row[6]; + Task startTask = (Task)row[7]; + ContextInstance contextInstance = (ContextInstance) row[8]; + + if (tenantService.isEnabled()) + { + try + { + tenantService.checkDomain(processDefinition.getName()); + } + catch (RuntimeException re) + { + // deliberately skip this one - due to domain mismatch - eg. when querying by group authority + return null; + } + } + // TaskInstance with some precached properties + TaskInstance helperTi = taskInstanceCache.get(ti.getId()); + + @SuppressWarnings("unchecked") + Map variables = variablesCache.get(contextInstance.getId()).getVariables(); + // WorkflowTaskProperies + Map properties = getTaskProperties(helperTi != null ? helperTi : ti, false, variablesCache); + + WorkflowDefinition wfDef = createWorkflowDefinition(processDefinition, startTask); + WorkflowInstance instance = createWorkflowInstance(processInstance, wfDef, null, variables); + WorkflowNode wfNode = createWorkflowNode(node); + WorkflowPath path = createWorkflowPath(token, instance, wfNode); + WorkflowTaskDefinition taskDef = createWorkflowTaskDefinition(task); + return createWorkflowTask(ti, taskDef, path, properties); + } + + private Map cacheVariables(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheVariablesNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheVariablesNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings({ "unchecked", "cast" }) + private void cacheVariablesNoBatch(Session session, List contextInstanceIds, Map variablesCache) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheInstanceVariables"); + query.setParameterList("ids", contextInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TokenVariableMap tokenVariableMap : results) + { + variablesCache.put(tokenVariableMap.getContextInstance().getId(), tokenVariableMap); + } + } + + private Map cacheTasks(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheTasksNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheTasksNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings({ "unchecked", "cast" }) + private void cacheTasksNoBatch(Session session, List taskInstanceIds, Map returnMap) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheTaskInstanceProperties"); + query.setParameterList("ids", taskInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TaskInstance taskInstance : results) + { + returnMap.put(taskInstance.getId(), taskInstance); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) */ @SuppressWarnings("unchecked") public List getPooledTasks(final List authorities) @@ -1506,7 +1674,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine for (Map.Entry prop : props.entrySet()) { Conjunction value = Restrictions.conjunction(); - value.add(Restrictions.eq("name", mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); value.add(Restrictions.eq("value", prop.getValue().toString())); values.add(value); } @@ -1535,7 +1703,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine for (Map.Entry prop : props.entrySet()) { Conjunction value = Restrictions.conjunction(); - value.add(Restrictions.eq("name", mapQNameToName(prop.getKey()))); + value.add(Restrictions.eq("name", factory.mapQNameToName(prop.getKey()))); value.add(Restrictions.eq("value", prop.getValue().toString())); values.add(value); } @@ -1786,7 +1954,6 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // create properties to set on task instance Map newProperties = new HashMap(10); - if(properties!=null) { newProperties.putAll(properties); @@ -2201,11 +2368,23 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /** * Gets Properties of Task * - * @param instance task instance - * @param properties properties to set + * @param instance task instance + * @param properties properties to set + */ + protected Map getTaskProperties(TaskInstance instance, boolean localProperties) + { + return getTaskProperties(instance, localProperties, null); + } + + /** + * Gets Properties of Task + * + * @param instance task instance + * @param properties properties to set + * @param variablesCache cahce of context instance variables if any exists */ @SuppressWarnings("unchecked") - protected Map getTaskProperties(TaskInstance instance, boolean localProperties) + protected Map getTaskProperties(TaskInstance instance, boolean localProperties, Map variablesCache) { // retrieve type definition for task TypeDefinition taskDef = getFullTaskDefinition(instance); @@ -2220,7 +2399,15 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine Token token = instance.getToken(); while (token != null) { - TokenVariableMap varMap = context.getTokenVariableMap(token); + TokenVariableMap varMap = null; + if (variablesCache != null && variablesCache.containsKey(context.getId())) + { + varMap = variablesCache.get(context.getId()); + } + else + { + varMap = context.getTokenVariableMap(token); + } if (varMap != null) { Map tokenVars = varMap.getVariablesLocally(); @@ -2241,7 +2428,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine for (Entry entry : vars.entrySet()) { String key = entry.getKey(); - QName qname = mapNameToQName(key); + QName qname = factory.mapNameToQName(key); // add variable, only if part of task definition or locally defined // on task @@ -2272,11 +2459,11 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine } // map jBPM task instance collections to associations - Set pooledActors = instance.getPooledActors(); + Set pooledActors = instance.getPooledActors(); if (pooledActors != null) { List pooledNodeRefs = new ArrayList(pooledActors.size()); - for (PooledActor pooledActor : (Set)pooledActors) + for (PooledActor pooledActor : pooledActors) { NodeRef pooledNodeRef = null; String pooledActorId = pooledActor.getActorId(); @@ -2487,7 +2674,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // no specific mapping to jBPM task has been established, so place // into // the generic task variable bag - String name = mapQNameToName(key); + String name = factory.mapQNameToName(key); instance.setVariableLocally(name, value); } } @@ -2531,7 +2718,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine if (description == null || description.length() == 0) { description = (String) instance.getContextInstance().getVariable( - mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); if (description != null && description.length() > 0) { defaultValues.put(WorkflowModel.PROP_DESCRIPTION, description); @@ -2562,7 +2749,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine if (description == null || description.length() == 0) { description = (String) instance.getContextInstance().getVariable( - mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); if (description != null && description.length() > 0) { Map defaultValues = new HashMap(); @@ -2583,30 +2770,30 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine Map taskProperties = getTaskProperties(startTask, true); ContextInstance processContext = startTask.getContextInstance(); - String workflowDescriptionName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); + String workflowDescriptionName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); if (!processContext.hasVariable(workflowDescriptionName)) { processContext.setVariable(workflowDescriptionName, taskProperties .get(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); } - String workflowDueDateName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); + String workflowDueDateName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE); if (!processContext.hasVariable(workflowDueDateName)) { processContext.setVariable(workflowDueDateName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); } - String workflowPriorityName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); + String workflowPriorityName = factory.mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY); if (!processContext.hasVariable(workflowPriorityName)) { processContext.setVariable(workflowPriorityName, taskProperties.get(WorkflowModel.PROP_WORKFLOW_PRIORITY)); } - String workflowPackageName = mapQNameToName(WorkflowModel.ASSOC_PACKAGE); + String workflowPackageName = factory.mapQNameToName(WorkflowModel.ASSOC_PACKAGE); if (!processContext.hasVariable(workflowPackageName)) { Serializable packageNodeRef = taskProperties.get(WorkflowModel.ASSOC_PACKAGE); processContext.setVariable(workflowPackageName, convertNodeRefs(packageNodeRef instanceof List, packageNodeRef)); } - String workflowContextName = mapQNameToName(WorkflowModel.PROP_CONTEXT); + String workflowContextName = factory.mapQNameToName(WorkflowModel.PROP_CONTEXT); if (!processContext.hasVariable(workflowContextName)) { Serializable contextRef = taskProperties.get(WorkflowModel.PROP_CONTEXT); @@ -2832,48 +3019,6 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine return name; } - /** - * Map jBPM variable name to QName - * - * @param name - * jBPM variable name - * @return qname - */ - private QName mapNameToQName(String name) - { - QName qname = null; - // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) - String qnameStr = (name.indexOf('}') == -1) ? name.replaceFirst("_", ":") : name.replace("}", ":"); - try - { - qname = QName.createQName(qnameStr, this.namespaceService); - } - catch(NamespaceException e) - { - qname = QName.createQName(name, this.namespaceService); - } - return qname; - } - - /** - * Map QName to jBPM variable name - * - * @param name - * QName - * @return jBPM variable name - */ - private String mapQNameToName(QName name) - { - // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as new scheme (i.e. } -> _) - // NOTE: Use new scheme - String nameStr = name.toPrefixString(this.namespaceService); - if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) - { - return nameStr.replace(':', '}'); - } - return nameStr.replace(':', '_'); - } - /** * Get an I18N Label for a workflow item * @@ -2936,14 +3081,32 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine * * @param token * JBoss JBPM Token + * @param wfInstance + * @param node * @return Workflow Path */ protected WorkflowPath createWorkflowPath(Token token) { - String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); - String id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId; + if(token == null) + return null; WorkflowInstance wfInstance = createWorkflowInstance(token.getProcessInstance()); WorkflowNode node = createWorkflowNode(token.getNode()); + return createWorkflowPath(token, wfInstance, node); + } + + /** + * Creates a Workflow Path + * + * @param token + * JBoss JBPM Token + * @param wfInstance + * @param node + * @return Workflow Path + */ + protected WorkflowPath createWorkflowPath(Token token, WorkflowInstance wfInstance, WorkflowNode node) + { + String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); + String id = token.getProcessInstance().getId() + WORKFLOW_PATH_SEPERATOR + tokenId; boolean isActive = !token.hasEnded(); return factory.createPath(id, wfInstance, node, isActive); } @@ -2957,6 +3120,8 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected WorkflowNode createWorkflowNode(Node node) { + if(node==null) + return null; String processName = node.getProcessDefinition().getName(); String name = node.getName(); String type = getRealNode(node).getClass().getSimpleName(); @@ -2989,6 +3154,8 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected WorkflowTransition createWorkflowTransition(Transition transition) { + if(transition==null) + return null; String id = transition.getName(); Node node = transition.getFrom(); boolean isDefault = node.getDefaultLeavingTransition().equals(transition); @@ -3014,23 +3181,31 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine return createWorkflowInstance(instance, null); } + @SuppressWarnings("unchecked") + private WorkflowInstance createWorkflowInstance(ProcessInstance instance, Date endDate) + { + if(instance == null) + return null; + + Map variables = instance.getContextInstance().getVariables(); + WorkflowDefinition definition = createWorkflowDefinition(instance.getProcessDefinition()); + return createWorkflowInstance(instance, definition, endDate, variables); + } + /** * Creates a Workflow Instance * * @param instance * JBoss JBPM Process Instance * @param endDate + * @param variables * @return Workflow instance */ - @SuppressWarnings("unchecked") - protected WorkflowInstance createWorkflowInstance(ProcessInstance instance, Date endDate) + protected WorkflowInstance createWorkflowInstance(ProcessInstance instance, WorkflowDefinition definition, Date endDate, Map variables) { if(instance == null) return null; - String id = Long.toString(instance.getId()); - Map variables = instance.getContextInstance().getVariables(); - WorkflowDefinition definition = createWorkflowDefinition(instance.getProcessDefinition()); Date startDate = instance.getStart(); boolean isActive = false; if (endDate == null) @@ -3050,41 +3225,66 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition) { + if(definition==null) + return null; + Task startTask = definition.getTaskMgmtDefinition().getStartTask(); + return createWorkflowDefinition(definition, startTask); + } + + /** + * Creates a Workflow Definition + * + * @param definition + * JBoss Process Definition + * @return Workflow Definition + */ + private WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition, Task startTask) + { + if(definition==null) + return null; String id = Long.toString(definition.getId()); String name = definition.getName(); int version = definition.getVersion(); - - Task startTask = definition.getTaskMgmtDefinition().getStartTask(); WorkflowTaskDefinition startTaskDef = createWorkflowTaskDefinition(startTask); return factory.createDefinition(id, name, version, name, null, startTaskDef); } + /** + * * Creates a Workflow Task + * @param task + * @return + */ + protected WorkflowTask createWorkflowTask(TaskInstance task) + { + WorkflowPath path = createWorkflowPath(task.getToken()); + Map properties = getTaskProperties(task, false); + WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); + return createWorkflowTask(task, definition, path, properties); + } + /** * Creates a Workflow Task * * @param task * JBoss Task Instance + * @param taskDef + * @param path + * @param properties * @return Workflow Task */ - protected WorkflowTask createWorkflowTask(TaskInstance task) + private WorkflowTask createWorkflowTask(TaskInstance task, WorkflowTaskDefinition definition, WorkflowPath path, Map properties) { if(task == null) return null; - String processName = task.getTask().getProcessDefinition().getName(); - if (tenantService.isEnabled()) { tenantService.checkDomain(processName); // throws exception if // domain mismatch } - String id = Long.toString(task.getId()); String name = task.getName(); - WorkflowPath path = createWorkflowPath(task.getToken()); WorkflowTaskState state = getWorkflowTaskState(task); - WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); - Map properties = getTaskProperties(task, false); return factory.createTask(id, definition, name, null, null, state, path, properties); } @@ -3129,6 +3329,9 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected WorkflowTimer createWorkflowTimer(Timer timer) { + if(timer==null) + return null; + WorkflowPath path = createWorkflowPath(timer.getToken()); WorkflowTask workflowTask = null; diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java new file mode 100644 index 0000000000..19f51d3ce3 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java @@ -0,0 +1,288 @@ +package org.alfresco.repo.workflow.jbpm; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Resource; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.hibernate.Query; +import org.hibernate.Session; +import org.jbpm.JbpmContext; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springmodules.workflow.jbpm31.JbpmCallback; +import org.springmodules.workflow.jbpm31.JbpmTemplate; + +/** + * This test shows a performance benefit from a usage of direct queries + * instead of creating required classes like WorkflowTask in a loop with collecting + * required properties from different services. + * + * @author arsenyko + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:alfresco/application-context.xml" }) +public class JBPMJunit4LoadTests +{ + + private static String WORKFLOW_NAME = "jbpm$wf:adhoc"; + private static String WORKFLOW_NODE_NAME = "workflow-test-19243cbb-c58a-485e-bcd9-2e2be030dfb9.txt"; + private static int WORKFLOW_COUNT = 2000; + + private static List workflowIds = null; + private static NodeRef rootNode = null; + + @Resource(name=ServiceRegistry.SERVICE_REGISTRY) + private ServiceRegistry serviceRegistry; + private RetryingTransactionHelper retryingTransactionHelper; + private static NodeService nodeService; + private static WorkflowService workflowService; + private FileFolderService fileFolderService; + + @Resource(name="repositoryHelper") + private Repository repositoryHelper; + + @Resource(name="jbpm_engine") + private JBPMEngine jbpmEngine; + + private NodeRef companyHomeNodeRef; + + @Before + public void setUp() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + retryingTransactionHelper = serviceRegistry.getRetryingTransactionHelper(); + fileFolderService = serviceRegistry.getFileFolderService(); + workflowService = serviceRegistry.getWorkflowService(); + nodeService = serviceRegistry.getNodeService(); + + companyHomeNodeRef = repositoryHelper.getCompanyHome(); + System.out.println(" -------------- "); + createWorkflowStuff(); + } + + public void createWorkflowStuff() throws Exception + { + System.out.println(" [createWorkflowStuff] Started at " + new Date().toString()); + + if (rootNode == null) + { + workflowIds =new ArrayList(); + RetryingTransactionCallback callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + FileInfo rootInfo = fileFolderService.create(companyHomeNodeRef, + WORKFLOW_NODE_NAME, + ContentModel.TYPE_FOLDER); + rootNode = rootInfo.getNodeRef(); + FileInfo contentInfo = fileFolderService.create(rootNode, + WORKFLOW_NODE_NAME, + ContentModel.TYPE_CONTENT); + NodeRef content = contentInfo.getNodeRef(); + ContentService contentService = serviceRegistry.getContentService(); + ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); + writer.setMimetype("text/plain"); + writer.setEncoding("UTF-8"); + writer.putContent("many workflows many workflows many workflows many workflows many workflows many workflows many workflows many workflows"); + System.out.println(" [createWorkflowStuff] Workflow root node '" + WORKFLOW_NODE_NAME + "' has been created"); + + WorkflowDefinition wfDef = jbpmEngine.getDefinitionByName(WORKFLOW_NAME); + long startTime = new Date().getTime(); + for (Integer i = 0; i < WORKFLOW_COUNT; i++) + { + // We are creating workflows in usual way, but with new persistent objects. + // There is a some performance issue with sesssion.flash() in each iteration, + // but this was made to avoid a lot of changes in a logic related to org.alfresco.service.cmr.workflow.* + // classes. + Map properties = prepareWorkflowProperties(rootNode, content, i.toString()); + WorkflowPath path = workflowService.startWorkflow(wfDef.getId(), properties); + workflowIds.add(path.getInstance().getId()); + // jbpmEngine.startWorkflow_ALF1787(wfDef.id, prepareWorkflowProperties(fileInfo.getNodeRef(), i.toString())); + } + long endTime = new Date().getTime(); + System.out.println(" [createWorkflowStuff] Execution time (ms): " + (endTime - startTime)); + return null; + } + + }; + retryingTransactionHelper.setMaxRetries(1); + retryingTransactionHelper.doInTransaction(callback); + System.out.println(" [createWorkflowStuff] Finished at " + new Date().toString()); + } + else + { + System.out.println(" [createWorkflowStuff] Workflow node '" + WORKFLOW_NODE_NAME + "' already exists"); + } + } + + //@Test +// public void testQuery1() throws Exception +// { +// RetryingTransactionCallback callback = new RetryingTransactionCallback(){ +// +// @Override +// public Void execute() throws Throwable +// { +// JbpmTemplate jbpmTemplate = (JbpmTemplate) applicationContext.getBean("jbpm_template"); +// List result = (List) jbpmTemplate.execute(new JbpmCallback() +// { +// public List doInJbpm(JbpmContext context) +// { +// Session session = context.getSession(); +// Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); +// return query.setString("actorId", "admin").list(); +// } +// }); +// for(Object[] ti : result) +// { +// System.out.println(Arrays.toString(ti)); +// } +// System.out.println(result.size()); +// return null; +// } +// }; +// retryingTransactionHelper.setMaxRetries(1); +// retryingTransactionHelper.doInTransaction(callback); +// } + + @Test + public void testGetAssignedTasks_NEW() throws Exception + { + final int RUN_COUNT = 7; + RetryingTransactionCallback callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + Date beginTime = new Date(); + System.out.println(" [testGetAssignedTasks_NEW] Started at " + beginTime.toString()); + List tasks = workflowService.getAssignedTasks("admin", WorkflowTaskState.IN_PROGRESS); + Date endTime = new Date(); + System.out.println(" [testGetAssignedTasks_NEW] Retrieved tasks: " + tasks.size() + " in " + (endTime.getTime() - beginTime.getTime()) + " ms"); + System.out.println(" [testGetAssignedTasks_NEW] Finished at " + endTime.toString()); + return null; + } + }; + retryingTransactionHelper.setMaxRetries(1); + for(int i=0; i callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + Date beginTime = new Date(); + System.out.println(" [testGetAssignedTasks_OLD] Started at " + beginTime.toString()); + List tasks = jbpmEngine.getAssignedTasks_OLD("admin", WorkflowTaskState.IN_PROGRESS); + Date endTime = new Date(); + System.out.println(" [testGetAssignedTasks_OLD] Retrieved tasks: " + tasks.size() + " in " + (endTime.getTime() - beginTime.getTime()) + " ms"); + System.out.println(" [testGetAssignedTasks_OLD] Finished at " + new Date().toString()); + return null; + } + }; + retryingTransactionHelper.setMaxRetries(1); + retryingTransactionHelper.doInTransaction(callback); + } + */ + + @After + public void tearDown() throws Exception + { + System.out.println(" -------------- "); + } + + private Map prepareWorkflowProperties(NodeRef root, NodeRef content, String id) + { + NodeRef packageRef = makePackage(root, content, id); + Map parameters = new HashMap(); + parameters.put(WorkflowModel.ASSOC_PACKAGE, packageRef); + parameters.put(WorkflowModel.ASSOC_ASSIGNEE, "admin"); + parameters.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Test workflow '" + id + "'"); + parameters.put(WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME, "test_workflow_" + id); + return parameters; + + } + + /** + * @param root + * @param content + * @param id + * @return + */ + private NodeRef makePackage(NodeRef root, NodeRef content, String id) + { + NodeRef container = fileFolderService.create(root, "package"+id, ContentModel.TYPE_FOLDER).getNodeRef(); + NodeRef packageRef = workflowService.createPackage(container); + nodeService.addChild(packageRef, content, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.DEFAULT_URI, id)); + return packageRef; + } + + @AfterClass + public static void cleanup() + { + // Clean up workflows + if(workflowIds !=null) + { + for (String wfId : workflowIds) + { + try + { + workflowService.cancelWorkflow(wfId); + } + catch(Exception e) + { + //NOOP + } + } + } + nodeService.deleteNode(rootNode); + } + +}