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;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.argeo.slc.SlcException;
27  import org.argeo.slc.execution.ExecutionContext;
28  import org.argeo.slc.execution.ExecutionFlow;
29  import org.argeo.slc.execution.ExecutionSpec;
30  import org.argeo.slc.execution.ExecutionSpecAttribute;
31  import org.springframework.beans.factory.BeanNameAware;
32  import org.springframework.beans.factory.InitializingBean;
33  import org.springframework.validation.MapBindingResult;
34  
35  /** Default implementation of an execution flow. */
36  public class DefaultExecutionFlow implements ExecutionFlow, InitializingBean,
37  		BeanNameAware {
38  	private final static Log log = LogFactory
39  			.getLog(DefaultExecutionFlow.class);
40  
41  	private final ExecutionSpec executionSpec;
42  	private String name = null;
43  	private Map<String, Object> parameters = new HashMap<String, Object>();
44  	private List<Runnable> executables = new ArrayList<Runnable>();
45  
46  	private String path;
47  
48  	private Boolean failOnError = true;
49  
50  	// Only needed if stacked execution flows are used
51  	private ExecutionContext executionContext = null;
52  
53  	public DefaultExecutionFlow() {
54  		this.executionSpec = new DefaultExecutionSpec();
55  	}
56  
57  	public DefaultExecutionFlow(ExecutionSpec executionSpec) {
58  		this.executionSpec = executionSpec;
59  	}
60  
61  	public DefaultExecutionFlow(ExecutionSpec executionSpec,
62  			Map<String, Object> parameters) {
63  		// be sure to have an execution spec
64  		this.executionSpec = (executionSpec == null) ? new DefaultExecutionSpec()
65  				: executionSpec;
66  
67  		// only parameters contained in the executionSpec can be set
68  		for (String parameter : parameters.keySet()) {
69  			if (!executionSpec.getAttributes().containsKey(parameter)) {
70  				throw new SlcException("Parameter " + parameter
71  						+ " is not defined in the ExecutionSpec");
72  			}
73  		}
74  
75  		// set the parameters
76  		this.parameters.putAll(parameters);
77  
78  		// check that all the required parameters are defined
79  		MapBindingResult errors = new MapBindingResult(parameters, "execution#"
80  				+ getName());
81  		for (String key : executionSpec.getAttributes().keySet()) {
82  			ExecutionSpecAttribute attr = executionSpec.getAttributes()
83  					.get(key);
84  
85  			if (attr.getIsImmutable() && !isSetAsParameter(key)) {
86  				errors.rejectValue(key, "Immutable but not set");
87  				break;
88  			}
89  
90  			if (attr.getIsConstant() && !isSetAsParameter(key)) {
91  				errors.rejectValue(key, "Constant but not set as parameter");
92  				break;
93  			}
94  
95  			if (attr.getIsHidden() && !isSetAsParameter(key)) {
96  				errors.rejectValue(key, "Hidden but not set as parameter");
97  				break;
98  			}
99  		}
100 
101 		if (errors.hasErrors())
102 			throw new SlcException("Could not prepare execution flow: "
103 					+ errors.toString());
104 
105 	}
106 
107 	public void run() {
108 		try {
109 			for (Runnable executable : executables) {
110 				if (Thread.interrupted()) {
111 					log.error("Flow '" + getName() + "' killed before '"
112 							+ executable + "'");
113 					Thread.currentThread().interrupt();
114 					return;
115 					// throw new ThreadDeath();
116 				}
117 				this.doExecuteRunnable(executable);
118 			}
119 		} catch (RuntimeException e) {
120 			if (Thread.interrupted()) {
121 				log.error("Flow '" + getName()
122 						+ "' killed while receiving an unrelated exception", e);
123 				Thread.currentThread().interrupt();
124 				return;
125 				// throw new ThreadDeath();
126 			}
127 			if (failOnError)
128 				throw e;
129 			else {
130 				log.error("Execution flow failed,"
131 						+ " but process did not fail"
132 						+ " because failOnError property"
133 						+ " is set to false: " + e);
134 				if (log.isTraceEnabled())
135 					e.printStackTrace();
136 			}
137 		}
138 	}
139 
140 	/**
141 	 * List sub-runnables that would be executed if run() method would be
142 	 * called.
143 	 */
144 	public Iterator<Runnable> runnables() {
145 		return executables.iterator();
146 	}
147 
148 	/**
149 	 * If there is one and only one runnable wrapped return it, throw an
150 	 * exeception otherwise.
151 	 */
152 	public Runnable getRunnable() {
153 		if (executables.size() == 1)
154 			return executables.get(0);
155 		else
156 			throw new SlcException("There are " + executables.size()
157 					+ " runnables in flow " + getName());
158 	}
159 
160 	public void doExecuteRunnable(Runnable runnable) {
161 		try {
162 			if (executionContext != null)
163 				if (runnable instanceof ExecutionFlow)
164 					executionContext.beforeFlow((ExecutionFlow) runnable);
165 			runnable.run();
166 		} finally {
167 			if (executionContext != null)
168 				if (runnable instanceof ExecutionFlow)
169 					executionContext.afterFlow((ExecutionFlow) runnable);
170 		}
171 	}
172 
173 	public void afterPropertiesSet() throws Exception {
174 		if (path == null) {
175 			if (name.charAt(0) == '/') {
176 				path = name.substring(0, name.lastIndexOf('/'));
177 			}
178 		}
179 
180 		if (path != null) {
181 			for (Runnable executable : executables) {
182 				if (executable instanceof DefaultExecutionFlow) {
183 					// so we don't need to have DefaultExecutionFlow
184 					// implementing StructureAware
185 					// FIXME: probably has side effects
186 					DefaultExecutionFlow flow = (DefaultExecutionFlow) executable;
187 					String newPath = path + '/' + flow.getName();
188 					flow.setPath(newPath);
189 					log.warn(newPath + " was forcibly set on " + flow);
190 				}
191 			}
192 		}
193 	}
194 
195 	public void setBeanName(String name) {
196 		this.name = name;
197 	}
198 
199 	public void setExecutables(List<Runnable> executables) {
200 		this.executables = executables;
201 	}
202 
203 	public void setParameters(Map<String, Object> attributes) {
204 		this.parameters = attributes;
205 	}
206 
207 	public String getName() {
208 		return name;
209 	}
210 
211 	public ExecutionSpec getExecutionSpec() {
212 		return executionSpec;
213 	}
214 
215 	public Object getParameter(String parameterName) {
216 		// Verify that there is a spec attribute
217 		ExecutionSpecAttribute specAttr = null;
218 		if (executionSpec.getAttributes().containsKey(parameterName)) {
219 			specAttr = executionSpec.getAttributes().get(parameterName);
220 		} else {
221 			throw new SlcException("Key " + parameterName
222 					+ " is not defined in the specifications of " + toString());
223 		}
224 
225 		if (parameters.containsKey(parameterName)) {
226 			Object paramValue = parameters.get(parameterName);
227 			return paramValue;
228 		} else {
229 			if (specAttr.getValue() != null) {
230 				return specAttr.getValue();
231 			}
232 		}
233 		throw new SlcException("Key " + parameterName
234 				+ " is not set as parameter in " + toString());
235 	}
236 
237 	public Boolean isSetAsParameter(String key) {
238 		return parameters.containsKey(key)
239 				|| (executionSpec.getAttributes().containsKey(key) && executionSpec
240 						.getAttributes().get(key).getValue() != null);
241 	}
242 
243 	@Override
244 	public String toString() {
245 		return new StringBuffer("Execution flow ").append(name).toString();
246 	}
247 
248 	@Override
249 	public boolean equals(Object obj) {
250 		return ((ExecutionFlow) obj).getName().equals(name);
251 	}
252 
253 	@Override
254 	public int hashCode() {
255 		return name.hashCode();
256 	}
257 
258 	public String getPath() {
259 		return path;
260 	}
261 
262 	public void setPath(String path) {
263 		this.path = path;
264 	}
265 
266 	public Boolean getFailOnError() {
267 		return failOnError;
268 	}
269 
270 	public void setFailOnError(Boolean failOnError) {
271 		this.failOnError = failOnError;
272 	}
273 
274 	public void setExecutionContext(ExecutionContext executionContext) {
275 		this.executionContext = executionContext;
276 	}
277 
278 }