From 78c26de935d15dafb2a6bfd81be4d3bb3ae61b23 Mon Sep 17 00:00:00 2001 From: Olivier Capillon Date: Mon, 19 Oct 2009 18:28:39 +0000 Subject: [PATCH] Add Flow Generator git-svn-id: https://svn.argeo.org/slc/trunk@3046 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../generator/CompositeRunnableFactory.java | 62 ++++++ .../generator/DefaultRunnableDataNode.java | 89 ++++++++ .../generator/ExecutionFlowGenerator.java | 155 ++++++++++++++ .../ExecutionFlowGeneratorSource.java | 24 +++ .../execution/generator/RunnableCall.java | 55 +++++ .../execution/generator/RunnableCallFlow.java | 190 ++++++++++++++++++ .../generator/RunnableCallFlowDescriptor.java | 55 +++++ .../execution/generator/RunnableDataNode.java | 59 ++++++ .../execution/generator/RunnableFactory.java | 14 ++ 9 files changed, 703 insertions(+) create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/CompositeRunnableFactory.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/DefaultRunnableDataNode.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGenerator.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGeneratorSource.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCall.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlow.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlowDescriptor.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableDataNode.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableFactory.java diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/CompositeRunnableFactory.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/CompositeRunnableFactory.java new file mode 100644 index 000000000..7a43f6633 --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/CompositeRunnableFactory.java @@ -0,0 +1,62 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.Map; + +import org.argeo.slc.SlcException; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +/** + * Composite RunnableFactory, redirecting the Runnable + * creation to on of the configured RunnableFactory depending + * on an entry of the data of the RunnableDataNode. + */ +public class CompositeRunnableFactory implements RunnableFactory { + + /** + * Key used to access factory ID in the data of the RunnableDataNode + */ + private String factoryKey; + + /** + * Maps a factory ID to an ExecutionFlowFactory + */ + private Map factories; + + public void createAndRegisterRunnable(RunnableDataNode node, + BeanDefinitionRegistry beanDefinitionRegistry) { + findFactory(node).createAndRegisterRunnable(node, beanDefinitionRegistry); + } + + /** + * Finds the RunnableFactory to use for a RunnableDataNode + * @param node + * @return the RunnableFactory to use for the RunnableDataNode + */ + private RunnableFactory findFactory(RunnableDataNode node) { + // get the factory ID from the data of the RunnableDescriptor + Map data = node.getData(); + if (!data.containsKey(factoryKey)) { + throw new SlcException("No data value for key '" + factoryKey + "'"); + } + String factoryId = data.get(factoryKey).toString(); + + // see if we have a factory for the factory ID + if ((factories != null) && factories.containsKey(factoryId)) { + return factories.get(factoryId); + } + // if not, look for a bean of name equals to the factory ID + else { + throw new SlcException("Not implemented"); + } + } + + public void setFactoryKey(String factoryKey) { + this.factoryKey = factoryKey; + } + + public void setFactories(Map factories) { + this.factories = factories; + } + + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/DefaultRunnableDataNode.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/DefaultRunnableDataNode.java new file mode 100644 index 000000000..da20f18b1 --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/DefaultRunnableDataNode.java @@ -0,0 +1,89 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Default implementation of RunnableDataNode + * + */ +public class DefaultRunnableDataNode implements RunnableDataNode { + + private List children = new ArrayList(); + + private RunnableDataNode parent; + + /** + * Data of the RunnableDataNode. Does not contain + * parent data. + */ + private Map properData = new HashMap(); + + private String path; + + private String beanName; + + public boolean isLeaf() { + return children.size() == 0; + } + + public List getChildren() { + return children; + } + + public void addChild(RunnableDataNode child) { + child.setParent(this); + children.add(child); + } + + public Map getData() { + Map data = new HashMap(); + if(parent != null) { + Map parentData = parent.getData(); + if(parentData != null) { + data.putAll(parentData); + } + } + // entries defined in parentData can be overridden + // in properData + if(properData != null) { + data.putAll(properData); + } + return data; + } + + public Map getProperData() { + return properData; + } + + public void setProperData(Map properData) { + this.properData = properData; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getBeanName() { + return beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public void setParent(RunnableDataNode parent) { + this.parent = parent; + } + + public RunnableDataNode getParent() { + return parent; + } + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGenerator.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGenerator.java new file mode 100644 index 000000000..5451402ff --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGenerator.java @@ -0,0 +1,155 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.HashMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.slc.SlcException; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; + +/** + * Generates ExecutionFlows and Runnables as + * beans in the Spring Application Context. + * Called by the Application Context as a BeanFactoryPostProcessor. + * Two kinds of beans are generated: + * RunnableCallFlow, calling a list of Runnables from the + * Application Context after configuring the ExecutionContext, + * and outputs of a RunnableFactory. + */ +/** + * @author Olivier Capillon + * + */ +public class ExecutionFlowGenerator implements BeanFactoryPostProcessor, + PriorityOrdered { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * Source providing a list of RunnableCallFlowDescriptor + * used to create RunnableCallFlow and a list of + * RunnableDataNode used to create any kind of flow via a factory + */ + protected ExecutionFlowGeneratorSource source; + + /** + * Factory used to create Runnables in the Application context from + * the RunnableDataNode provided from the source. + */ + protected RunnableFactory runnableFactory; + + /** + * Bean name of the ExecutionContext. + * Used to provide the created RunnableCallFlow beans + * with a RuntimeBeanReference to + * the ExecutionContext + */ + private String executionContextBeanName = "executionContext"; + + /** + * Bean name of the context values Map. + * A bean of class HashMap is created with this name, and a + * RuntimeBeanReference is provided to the created + * RunnableCallFlow beans. + */ + private String contextValuesBeanName = "executionFlowGenerator.contextValues"; + + public void postProcessBeanFactory( + ConfigurableListableBeanFactory beanFactory) throws BeansException { + + // assert that the beanFactory is a BeanDefinitionRegistry + if (!(beanFactory instanceof BeanDefinitionRegistry)) { + throw new SlcException("Can only work on " + + BeanDefinitionRegistry.class); + } + + // add bean for the Context Values Map + createAndRegisterContextValuesBean((BeanDefinitionRegistry) beanFactory); + + // add beans for each RunnableDataNode + for(RunnableDataNode node : source.getRunnableDataNodes()) { + runnableFactory.createAndRegisterRunnable(node, (BeanDefinitionRegistry) beanFactory); + } + + // add beans for each RunnableCallFlowDescriptor of the source to the application context + for (RunnableCallFlowDescriptor descriptor : source + .getRunnableCallFlowDescriptors()) { + createAndRegisterFlowFor(descriptor, (BeanDefinitionRegistry) beanFactory); + } + } + + /** + * Creates a RunnableCallFlow bean + * for a RunnableCallFlowDescriptor and registers + * it in the BeanDefinitionRegistry + * @param flowDescriptor + * @param registry + */ + private void createAndRegisterFlowFor(RunnableCallFlowDescriptor flowDescriptor, BeanDefinitionRegistry registry) { + // create the flow bean + GenericBeanDefinition flowBean = new GenericBeanDefinition(); + flowBean.setBeanClass(RunnableCallFlow.class); + + MutablePropertyValues mpv = new MutablePropertyValues(); + mpv.addPropertyValue("runnableCalls", flowDescriptor.getRunnableCalls()); + mpv.addPropertyValue("sharedContextValuesMap", new RuntimeBeanReference(contextValuesBeanName)); + + mpv.addPropertyValue("name", flowDescriptor.getBeanName()); + mpv.addPropertyValue("path", flowDescriptor.getPath()); + + mpv.addPropertyValue("executionContext", new RuntimeBeanReference(executionContextBeanName)); + + flowBean.setPropertyValues(mpv); + + // register it + if(log.isDebugEnabled()) { + log.debug("Registering bean definition for RunnableCallFlow " + flowDescriptor.getBeanName()); + } + registry.registerBeanDefinition(flowDescriptor.getBeanName(), flowBean); + } + + /** + * Creates the Context Values bean and register it in the + * BeanDefinitionRegistry + * @param registry + */ + private void createAndRegisterContextValuesBean(BeanDefinitionRegistry registry) { + GenericBeanDefinition contextValuesBean = new GenericBeanDefinition(); + contextValuesBean.setBeanClass(HashMap.class); + + BeanDefinitionHolder bdh = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(contextValuesBean, contextValuesBeanName), registry, true); + registry.registerBeanDefinition(contextValuesBeanName, bdh.getBeanDefinition()); + } + + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + public void setSource(ExecutionFlowGeneratorSource source) { + this.source = source; + } + + public void setRunnableFactory(RunnableFactory runnableFactory) { + this.runnableFactory = runnableFactory; + } + + public void setExecutionContextBeanName(String executionContextBeanName) { + this.executionContextBeanName = executionContextBeanName; + } + + public void setContextValuesBeanName(String contextValuesBeanName) { + this.contextValuesBeanName = contextValuesBeanName; + } + + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGeneratorSource.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGeneratorSource.java new file mode 100644 index 000000000..8dc5d7d55 --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/ExecutionFlowGeneratorSource.java @@ -0,0 +1,24 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.List; + +/** + * Provides 2 types of information required by an ExecutionFlowGenerator: + * a list of RunnableCallFlowDescriptor used to create RunnableCallFlow + * and a list of RunnableDataNode used to create any kind of flow via a factory. + */ +public interface ExecutionFlowGeneratorSource { + + /** + * @return a list of RunnableCallFlowDescriptor used + * by a ExecutionFlowGenerator to create RunnableCallFlow + */ + public List getRunnableCallFlowDescriptors(); + + /** + * @return a list of RunnableDataNode used + * by a ExecutionFlowGenerator to create any kind of flow via a factory + */ + public List getRunnableDataNodes(); + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCall.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCall.java new file mode 100644 index 000000000..d44ff2e3c --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCall.java @@ -0,0 +1,55 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.Map; + +/** + * Storage class for information required to call a flow + * of the Spring execution context: + * bean name of the flow, + * variables to add to the Execution Context before the call + * and variables (context values) to add to a Map + * potentially referenced by the called flow + */ +public class RunnableCall { + + /** + * Bean name of the flow to call + */ + private String beanName; + + /** + * Variables to add to the execution context before the call + */ + private Map executionVariables; + + /** + * Variables to add to the Map potentially referenced by + * the called flow + */ + private Map contextValues; + + public String getBeanName() { + return beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public Map getExecutionVariables() { + return executionVariables; + } + + public void setExecutionVariables(Map executionVariables) { + this.executionVariables = executionVariables; + } + + public Map getContextValues() { + return contextValues; + } + + public void setContextValues(Map contextValues) { + this.contextValues = contextValues; + } + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlow.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlow.java new file mode 100644 index 000000000..4e58bfc14 --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlow.java @@ -0,0 +1,190 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.slc.SlcException; +import org.argeo.slc.core.execution.DefaultExecutionSpec; +import org.argeo.slc.execution.ExecutionContext; +import org.argeo.slc.execution.ExecutionFlow; +import org.argeo.slc.execution.ExecutionSpec; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Execution Flow calling a list of Runnable (identified by their bean + * name in the Spring Application Context) after configuring the Execution + * context and a Map potentially shared by the called Runnable + * + */ +public class RunnableCallFlow implements ExecutionFlow, ApplicationContextAware { + + private final static Log log = LogFactory.getLog(RunnableCallFlow.class); + + /** + * Key in the execution context for the index of the call (e.g. 0 + * for the first runnable called, ...) + */ + public final static String VAR_CALL_INDEX = "slcVar.runnableCallFlow.callIndex"; + + /** + * Name of the flow. Also bean name + */ + private String name; + + /** + * Path of the flow + */ + private String path; + + /** + * Whether an exception in a Runnable shall + * stop the execution of the flow + */ + private Boolean failOnError = true; + + /** + * List of Runnable to call, with bean name, execution variables and + * context values + */ + private List runnableCalls; + + /** + * Map potentially referenced by called flows. Updated with + * the context values of a Runnable before calling it. + */ + private Map sharedContextValuesMap; + + /** + * ExecutionSpec of the flow. Does not contain any + * attribute. + */ + private ExecutionSpec executionSpec = new DefaultExecutionSpec(); + + /** + * Reference to the ExecutionContext + */ + private ExecutionContext executionContext; + + /** + * Reference to the Spring ApplicationContext. + * Set via setApplicationContext, the class implementing + * ApplicationContextAware + */ + private ApplicationContext applicationContext; + + /** + * Runs a Runnable after configuring the Execution Context + * and sharedContextValuesMap + * @param runnable the Runnable to call + * @param executionVariables the variables to add to the ExecutionContext + * @param contextValues the variables to add to sharedContextValuesMap + * @param callIndex index of the call (0 for the first called Runnable) + * set as variable of the ExecutionContext + */ + private void run(Runnable runnable, Map executionVariables, Map contextValues, int callIndex) { + // add all variables to the Execution Context + for(Map.Entry entry : executionVariables.entrySet()) { + executionContext.setVariable(entry.getKey(), entry.getValue()); + } + + // add call Index Variable + executionContext.setVariable(VAR_CALL_INDEX, callIndex); + + // clear sharedContextValues and add all values of contextValues + if(sharedContextValuesMap != null) { + sharedContextValuesMap.clear(); + sharedContextValuesMap.putAll(contextValues); + } + + // then run the runnable + runnable.run(); + } + + /** + * Executes the flow. + * For each RunnableCall, the corresponding flow + * is retrieved from the Spring Application Context, the + * ExecutionContext and sharedContextValuesMap + * are configured and the Runnable is called. + */ + public void run() { + if (applicationContext == null) { + throw new SlcException("No ApplicationContext defined"); + } + + try { + for(int callIndex = 0; callIndex < runnableCalls.size(); ++callIndex) { + RunnableCall runnableCall = runnableCalls.get(callIndex); + Object bean = applicationContext.getBean(runnableCall.getBeanName(), Runnable.class); + if(log.isDebugEnabled()) + log.debug("Running flow '" + runnableCall.getBeanName() + "'"); + run((Runnable)bean, runnableCall.getExecutionVariables(), runnableCall.getContextValues(), callIndex); + } + } catch (RuntimeException e) { + if (failOnError) + throw e; + else { + log.error("Execution flow failed," + + " but process did not fail" + + " because failOnError property" + + " is set to false: " + e); + if (log.isTraceEnabled()) + e.printStackTrace(); + } + } + } + + public ExecutionSpec getExecutionSpec() { + return executionSpec; + } + + public String getName() { + return name; + } + + public Object getParameter(String key) { + throw new SlcException("RunnableCallFlow have no parameters"); + } + + public String getPath() { + return path; + } + + public Boolean isSetAsParameter(String key) { + // The ExecutionSpec having no attribute, + // always return false + return false; + } + + public void setName(String name) { + this.name = name; + } + + public void setPath(String path) { + this.path = path; + } + + public void setExecutionContext(ExecutionContext executionContext) { + this.executionContext = executionContext; + } + + public void setRunnableCalls(List runnableCalls) { + this.runnableCalls = runnableCalls; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public void setSharedContextValuesMap(Map contextValues) { + this.sharedContextValuesMap = contextValues; + } + + public void setFailOnError(Boolean failOnError) { + this.failOnError = failOnError; + } + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlowDescriptor.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlowDescriptor.java new file mode 100644 index 000000000..68f9cb97f --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableCallFlowDescriptor.java @@ -0,0 +1,55 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Storage Class for information required to + * instantiate a RunnableCallFlow: + * bean name of the flow, + * path of the flow + * and list of RunnableCall. + * + */ +public class RunnableCallFlowDescriptor { + + /** + * Bean name of the flow to instantiate + */ + private String beanName; + + /** + * Path of the flow to instantiate + */ + private String path; + + /** + * List of RunnableCall + */ + private List runnableCalls = new ArrayList(); + + public String getBeanName() { + return beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getRunnableCalls() { + return runnableCalls; + } + + public void setRunnableCalls(List runnableCalls) { + this.runnableCalls = runnableCalls; + } + +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableDataNode.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableDataNode.java new file mode 100644 index 000000000..25fee91a0 --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableDataNode.java @@ -0,0 +1,59 @@ +package org.argeo.slc.core.execution.generator; + +import java.util.List; +import java.util.Map; + +/** + * Stores information relative to a Runnable. + * Allows to structure the information as a tree, each node + * storing data as a Map. + */ +public interface RunnableDataNode { + + /** + * @return a Map containing the data associated with this node. + * Data associated with parent nodes are expected + * to be contained in the returned Map + */ + public Map getData(); + + /** + * @return the name of the bean to create. + * Can be null if no bean shall be created for the + * RunnableDataNode (e.g. is is a sub-node) + */ + public String getBeanName(); + + /** + * @return the path of the flow bean to create. + * Can be null if the bean to created is not an + * ExecutionFlow or if no bean shall be created for the + * RunnableDataNode (e.g. is is a sub-node) + */ + public String getPath(); + + /** + * @return whether the RunnableDataNode has + * children or not. + * Expected to be equivalent to getChildren().empty() + */ + public boolean isLeaf(); + + /** + * @return the list of RunnableDataNode children. + * Can be empty. Shall not be null. + */ + public List getChildren(); + + /** + * @return the RunnableDataNode parent. + * Can be null if no parent is defined (top node). + */ + public RunnableDataNode getParent(); + + /** + * Sets the RunnableDataNode parent + * @param parent + */ + public void setParent(RunnableDataNode parent); +} diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableFactory.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableFactory.java new file mode 100644 index 000000000..cd0af706d --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/generator/RunnableFactory.java @@ -0,0 +1,14 @@ +package org.argeo.slc.core.execution.generator; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +/** + * Interprets a RunnableDataNode by creating corresponding + * beans and registering them in a BeanDefinitionRegistry + * + */ +public interface RunnableFactory { + + public void createAndRegisterRunnable(RunnableDataNode node, + BeanDefinitionRegistry beanDefinitionRegistry); +} -- 2.39.2