View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.core.execution.generator;
17  
18  import java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.argeo.slc.SlcException;
26  import org.argeo.slc.core.execution.DefaultExecutionSpec;
27  import org.argeo.slc.execution.ExecutionContext;
28  import org.argeo.slc.execution.ExecutionFlow;
29  import org.argeo.slc.execution.ExecutionSpec;
30  import org.springframework.context.ApplicationContext;
31  import org.springframework.context.ApplicationContextAware;
32  
33  /**
34   * Execution Flow calling a list of <code>Runnable</code> (identified by their
35   * bean name in the Spring Application Context) after configuring the Execution
36   * context and a Map potentially shared by the called <code>Runnable</code>
37   * 
38   */
39  public class RunnableCallFlow implements ExecutionFlow, ApplicationContextAware {
40  
41  	private final static Log log = LogFactory.getLog(RunnableCallFlow.class);
42  
43  	/**
44  	 * Key in the execution context for the index of the call (e.g. 0 for the
45  	 * first runnable called, ...)
46  	 */
47  	public final static String VAR_CALL_INDEX = "slcVar.runnableCallFlow.callIndex";
48  
49  	/**
50  	 * Name of the flow. Also bean name
51  	 */
52  	private String name;
53  
54  	/**
55  	 * Path of the flow
56  	 */
57  	private String path;
58  
59  	/**
60  	 * Whether an exception in a <code>Runnable</code> shall stop the execution
61  	 * of the flow
62  	 */
63  	private Boolean failOnError = true;
64  
65  	/**
66  	 * List of <code>Runnable</code> to call, with bean name, execution
67  	 * variables and context values
68  	 */
69  	private List<RunnableCall> runnableCalls;
70  
71  	/**
72  	 * Map potentially referenced by called flows. Updated with the context
73  	 * values of a Runnable before calling it.
74  	 */
75  	private Map<String, Object> sharedContextValuesMap;
76  
77  	/**
78  	 * ExecutionSpec of the flow. Does not contain any attribute.
79  	 */
80  	private ExecutionSpec executionSpec = new DefaultExecutionSpec();
81  
82  	/**
83  	 * Reference to the ExecutionContext
84  	 */
85  	private ExecutionContext executionContext;
86  
87  	/**
88  	 * Reference to the Spring <code>ApplicationContext</code>. Set via
89  	 * <code>setApplicationContext</code>, the class implementing
90  	 * <code>ApplicationContextAware</code>
91  	 */
92  	private ApplicationContext applicationContext;
93  
94  	/**
95  	 * Runs a <code>Runnable</code> after configuring the Execution Context and
96  	 * <code>sharedContextValuesMap</code>
97  	 * 
98  	 * @param runnable
99  	 *            the <code>Runnable</code> to call
100 	 * @param executionVariables
101 	 *            the variables to add to the <code>ExecutionContext</code>
102 	 * @param contextValues
103 	 *            the variables to add to <code>sharedContextValuesMap</code>
104 	 * @param callIndex
105 	 *            index of the call (0 for the first called
106 	 *            <code>Runnable</code>) set as variable of the
107 	 *            <code>ExecutionContext</code>
108 	 */
109 	private void run(Runnable runnable, Map<String, Object> executionVariables,
110 			Map<String, Object> contextValues, int callIndex) {
111 		// add all variables to the Execution Context
112 		for (Map.Entry<String, Object> entry : executionVariables.entrySet()) {
113 			executionContext.setVariable(entry.getKey(), entry.getValue());
114 		}
115 
116 		// add call Index Variable
117 		executionContext.setVariable(VAR_CALL_INDEX, callIndex);
118 
119 		// clear sharedContextValues and add all values of contextValues
120 		if (sharedContextValuesMap != null) {
121 			sharedContextValuesMap.clear();
122 			sharedContextValuesMap.putAll(contextValues);
123 		}
124 
125 		// then run the runnable
126 		doExecuteRunnable(runnable);
127 	}
128 
129 	public void doExecuteRunnable(Runnable runnable) {
130 		runnable.run();
131 	}
132 
133 	/**
134 	 * Executes the flow. For each <code>RunnableCall</code>, the corresponding
135 	 * flow is retrieved from the Spring Application Context, the
136 	 * <code>ExecutionContext</code> and <code>sharedContextValuesMap</code> are
137 	 * configured and the <code>Runnable</code> is called.
138 	 */
139 	public void run() {
140 		if (applicationContext == null) {
141 			throw new SlcException("No ApplicationContext defined");
142 		}
143 
144 		try {
145 			for (int callIndex = 0; callIndex < runnableCalls.size(); ++callIndex) {
146 				RunnableCall runnableCall = runnableCalls.get(callIndex);
147 				Object bean = applicationContext.getBean(
148 						runnableCall.getBeanName(), Runnable.class);
149 				if (log.isDebugEnabled())
150 					log.debug("Running flow '" + runnableCall.getBeanName()
151 							+ "'");
152 				run((Runnable) bean, runnableCall.getExecutionVariables(),
153 						runnableCall.getContextValues(), callIndex);
154 			}
155 		} catch (RuntimeException e) {
156 			if (failOnError)
157 				throw e;
158 			else {
159 				log.error("Execution flow failed,"
160 						+ " but process did not fail"
161 						+ " because failOnError property"
162 						+ " is set to false: " + e);
163 				if (log.isTraceEnabled())
164 					e.printStackTrace();
165 			}
166 		}
167 	}
168 
169 	public Iterator<Runnable> runnables() {
170 		List<Runnable> runnables = new ArrayList<Runnable>();
171 		for (int callIndex = 0; callIndex < runnableCalls.size(); ++callIndex) {
172 			RunnableCall runnableCall = runnableCalls.get(callIndex);
173 			Object bean = applicationContext.getBean(
174 					runnableCall.getBeanName(), Runnable.class);
175 			runnables.add((Runnable) bean);
176 		}
177 		return runnables.iterator();
178 	}
179 
180 	public Runnable getRunnable() {
181 		if (runnableCalls.size() == 1)
182 			return runnables().next();
183 		else
184 			throw new SlcException("There are " + runnableCalls.size()
185 					+ " runnables in flow " + getName());
186 	}
187 
188 	@Override
189 	public String toString() {
190 		return new StringBuffer("RunnableCallFlow ").append(name).toString();
191 	}
192 
193 	public ExecutionSpec getExecutionSpec() {
194 		return executionSpec;
195 	}
196 
197 	public String getName() {
198 		return name;
199 	}
200 
201 	public Object getParameter(String key) {
202 		throw new SlcException("RunnableCallFlow have no parameters");
203 	}
204 
205 	public String getPath() {
206 		return path;
207 	}
208 
209 	public Boolean isSetAsParameter(String key) {
210 		// The ExecutionSpec having no attribute,
211 		// always return false
212 		return false;
213 	}
214 
215 	public void setName(String name) {
216 		this.name = name;
217 	}
218 
219 	public void setPath(String path) {
220 		this.path = path;
221 	}
222 
223 	public void setExecutionContext(ExecutionContext executionContext) {
224 		this.executionContext = executionContext;
225 	}
226 
227 	public void setRunnableCalls(List<RunnableCall> runnableCalls) {
228 		this.runnableCalls = runnableCalls;
229 	}
230 
231 	public void setApplicationContext(ApplicationContext applicationContext) {
232 		this.applicationContext = applicationContext;
233 	}
234 
235 	public void setSharedContextValuesMap(Map<String, Object> contextValues) {
236 		this.sharedContextValuesMap = contextValues;
237 	}
238 
239 	public void setFailOnError(Boolean failOnError) {
240 		this.failOnError = failOnError;
241 	}
242 
243 }