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.Comparator;
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.SortedSet;
22  import java.util.TreeMap;
23  import java.util.TreeSet;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.argeo.slc.execution.ExecutionFlow;
28  import org.argeo.slc.execution.ExecutionFlowDescriptor;
29  import org.argeo.slc.execution.ExecutionFlowDescriptorConverter;
30  import org.argeo.slc.execution.ExecutionModuleDescriptor;
31  import org.argeo.slc.execution.ExecutionSpec;
32  import org.argeo.slc.execution.ExecutionSpecAttribute;
33  import org.springframework.aop.scope.ScopedObject;
34  import org.springframework.beans.BeansException;
35  import org.springframework.beans.factory.BeanFactory;
36  import org.springframework.beans.factory.config.BeanDefinition;
37  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
38  import org.springframework.context.ApplicationContext;
39  import org.springframework.context.ApplicationContextAware;
40  import org.springframework.context.ConfigurableApplicationContext;
41  import org.springframework.util.StringUtils;
42  
43  /**
44   * Performs conversion in both direction between data exchanged with the agent
45   * and the data in the application context.
46   */
47  public class DefaultExecutionFlowDescriptorConverter implements
48  		ExecutionFlowDescriptorConverter, ApplicationContextAware {
49  	public final static String REF_VALUE_TYPE_BEAN_NAME = "beanName";
50  
51  	/** Workaround for https://www.spartadn.com/bugzilla/show_bug.cgi?id=206 */
52  	private final static String REF_VALUE_INTERNAL = "[internal]";
53  
54  	private final static Log log = LogFactory
55  			.getLog(DefaultExecutionFlowDescriptorConverter.class);
56  
57  	private ApplicationContext applicationContext;
58  
59  	@SuppressWarnings("unused")
60  	public Map<String, Object> convertValues(
61  			ExecutionFlowDescriptor executionFlowDescriptor) {
62  		Map<String, Object> values = executionFlowDescriptor.getValues();
63  		Map<String, Object> convertedValues = new HashMap<String, Object>();
64  		ExecutionSpec executionSpec = executionFlowDescriptor
65  				.getExecutionSpec();
66  
67  		if (executionSpec == null && log.isTraceEnabled())
68  			log.warn("Execution spec is null for " + executionFlowDescriptor);
69  
70  		if (values != null && executionSpec != null) {
71  			values: for (String key : values.keySet()) {
72  				ExecutionSpecAttribute attribute = executionSpec
73  						.getAttributes().get(key);
74  
75  				if (attribute == null)
76  					throw new FlowConfigurationException(
77  							"No spec attribute defined for '" + key + "'");
78  
79  				if (attribute.getIsConstant())
80  					continue values;
81  
82  				Object value = values.get(key);
83  				if (value instanceof PrimitiveValue) {
84  					PrimitiveValue primitiveValue = (PrimitiveValue) value;
85  					// TODO: check class <=> type
86  					convertedValues.put(key, primitiveValue.getValue());
87  				} else if (value instanceof RefValue) {
88  					RefValue refValue = (RefValue) value;
89  					String type = refValue.getType();
90  					if (REF_VALUE_TYPE_BEAN_NAME.equals(type)) {
91  						// FIXME: UI should send all information about spec
92  						// - targetClass
93  						// - name
94  						// String executionSpecName = executionSpec.getName();
95  						// ExecutionSpec localSpec = (ExecutionSpec)
96  						// applicationContext
97  						// .getBean(executionSpecName);
98  						// RefSpecAttribute localAttr = (RefSpecAttribute)
99  						// localSpec
100 						// .getAttributes().get(key);
101 						// Class<?> targetClass = localAttr.getTargetClass();
102 						//
103 						// String primitiveType = PrimitiveUtils
104 						// .classAsType(targetClass);
105 						String primitiveType = null;
106 						if (primitiveType != null) {
107 							// not active
108 							String ref = refValue.getRef();
109 							Object obj = PrimitiveUtils.convert(primitiveType,
110 									ref);
111 							convertedValues.put(key, obj);
112 						} else {
113 							String ref = refValue.getRef();
114 							if (ref != null && !ref.equals(REF_VALUE_INTERNAL)) {
115 								Object obj = null;
116 								if (applicationContext.containsBean(ref)) {
117 									obj = applicationContext.getBean(ref);
118 								} else {
119 									// FIXME: hack in order to pass primitive
120 									obj = ref;
121 								}
122 								convertedValues.put(key, obj);
123 							} else {
124 								log.warn("Cannot interpret " + refValue);
125 							}
126 						}
127 					} else if (PrimitiveUtils.typeAsClass(type) != null) {
128 						String ref = refValue.getRef();
129 						Object obj = PrimitiveUtils.convert(type, ref);
130 						convertedValues.put(key, obj);
131 					} else {
132 						throw new FlowConfigurationException(
133 								"Ref value type not supported: "
134 										+ refValue.getType());
135 					}
136 				} else {
137 					// default is to take the value as is
138 					convertedValues.put(key, value);
139 				}
140 			}
141 		}
142 		return convertedValues;
143 	}
144 
145 	public void addFlowsToDescriptor(ExecutionModuleDescriptor md,
146 			Map<String, ExecutionFlow> executionFlows) {
147 		SortedSet<ExecutionFlowDescriptor> set = new TreeSet<ExecutionFlowDescriptor>(
148 				new ExecutionFlowDescriptorComparator());
149 		for (String name : executionFlows.keySet()) {
150 			ExecutionFlow executionFlow = executionFlows.get(name);
151 
152 			ExecutionFlowDescriptor efd = getExecutionFlowDescriptor(executionFlow);
153 			ExecutionSpec executionSpec = efd.getExecutionSpec();
154 
155 			// Add execution spec if necessary
156 			if (!md.getExecutionSpecs().contains(executionSpec))
157 				md.getExecutionSpecs().add(executionSpec);
158 
159 			// Add execution flow
160 			set.add(efd);
161 			// md.getExecutionFlows().add(efd);
162 		}
163 		md.getExecutionFlows().addAll(set);
164 	}
165 
166 	public ExecutionFlowDescriptor getExecutionFlowDescriptor(
167 			ExecutionFlow executionFlow) {
168 		if (executionFlow.getName() == null)
169 			throw new FlowConfigurationException("Flow name is null: "
170 					+ executionFlow);
171 		String name = executionFlow.getName();
172 
173 		ExecutionSpec executionSpec = executionFlow.getExecutionSpec();
174 		if (executionSpec == null)
175 			throw new FlowConfigurationException("Execution spec is null: "
176 					+ executionFlow);
177 		if (executionSpec.getName() == null)
178 			throw new FlowConfigurationException(
179 					"Execution spec name is null: " + executionSpec);
180 
181 		Map<String, Object> values = new TreeMap<String, Object>();
182 		for (String key : executionSpec.getAttributes().keySet()) {
183 			ExecutionSpecAttribute attribute = executionSpec.getAttributes()
184 					.get(key);
185 
186 			if (attribute instanceof PrimitiveSpecAttribute) {
187 				if (executionFlow.isSetAsParameter(key)) {
188 					Object value = executionFlow.getParameter(key);
189 					PrimitiveValue primitiveValue = new PrimitiveValue();
190 					primitiveValue.setType(((PrimitiveSpecAttribute) attribute)
191 							.getType());
192 					primitiveValue.setValue(value);
193 					values.put(key, primitiveValue);
194 				} else {
195 					// no need to add a primitive value if it is not set,
196 					// all necessary information is in the spec
197 				}
198 			} else if (attribute instanceof RefSpecAttribute) {
199 				if (attribute.getIsConstant()) {
200 					values.put(key, new RefValue(REF_VALUE_INTERNAL));
201 				} else
202 					values.put(
203 							key,
204 							buildRefValue((RefSpecAttribute) attribute,
205 									executionFlow, key));
206 			} else {
207 				throw new FlowConfigurationException(
208 						"Unkown spec attribute type " + attribute.getClass());
209 			}
210 
211 		}
212 
213 		ExecutionFlowDescriptor efd = new ExecutionFlowDescriptor(name, null,
214 				values, executionSpec);
215 		// Takes description from spring
216 		BeanFactory bf = getBeanFactory();
217 		if (bf != null) {
218 			BeanDefinition bd = getBeanFactory().getBeanDefinition(name);
219 			efd.setDescription(bd.getDescription());
220 		}
221 		return efd;
222 	}
223 
224 	protected RefValue buildRefValue(RefSpecAttribute rsa,
225 			ExecutionFlow executionFlow, String key) {
226 		RefValue refValue = new RefValue();
227 		// FIXME: UI should be able to deal with other types
228 		refValue.setType(REF_VALUE_TYPE_BEAN_NAME);
229 		Class<?> targetClass = rsa.getTargetClass();
230 		String primitiveType = PrimitiveUtils.classAsType(targetClass);
231 		if (primitiveType != null) {
232 			if (executionFlow.isSetAsParameter(key)) {
233 				Object value = executionFlow.getParameter(key);
234 				refValue.setRef(value.toString());
235 			}
236 			refValue.setType(primitiveType);
237 			return refValue;
238 		} else {
239 
240 			if (executionFlow.isSetAsParameter(key)) {
241 				String ref = null;
242 				Object value = executionFlow.getParameter(key);
243 				if (applicationContext == null) {
244 					log.warn("No application context declared, cannot scan ref value.");
245 					ref = value.toString();
246 				} else {
247 
248 					// look for a ref to the value
249 					Map<String, ?> beans = getBeanFactory()
250 							.getBeansOfType(targetClass, false, false);
251 					// TODO: also check scoped beans
252 					beans: for (String beanName : beans.keySet()) {
253 						Object obj = beans.get(beanName);
254 						if (value instanceof ScopedObject) {
255 							// don't call methods of the target of the scope
256 							if (obj instanceof ScopedObject)
257 								if (value == obj) {
258 									ref = beanName;
259 									break beans;
260 								}
261 						} else {
262 							if (obj.equals(value)) {
263 								ref = beanName;
264 								break beans;
265 							}
266 						}
267 					}
268 				}
269 				if (ref == null) {
270 					if (log.isTraceEnabled())
271 						log.trace("Cannot define reference for ref spec attribute "
272 								+ key
273 								+ " in "
274 								+ executionFlow
275 								+ " ("
276 								+ rsa
277 								+ ")."
278 								+ " If it is an inner bean consider put it frozen.");
279 					ref = REF_VALUE_INTERNAL;
280 				} else {
281 					if (log.isTraceEnabled())
282 						log.trace(ref
283 								+ " is the reference for ref spec attribute "
284 								+ key + " in " + executionFlow + " (" + rsa
285 								+ ")");
286 				}
287 				refValue.setRef(ref);
288 			}
289 			return refValue;
290 		}
291 	}
292 
293 	/** @return can be null */
294 	private ConfigurableListableBeanFactory getBeanFactory() {
295 		if (applicationContext == null)
296 			return null;
297 		return ((ConfigurableApplicationContext) applicationContext)
298 				.getBeanFactory();
299 	}
300 
301 	/** Must be use within the execution application context */
302 	public void setApplicationContext(ApplicationContext applicationContext)
303 			throws BeansException {
304 		this.applicationContext = applicationContext;
305 	}
306 
307 	private static class ExecutionFlowDescriptorComparator implements
308 			Comparator<ExecutionFlowDescriptor> {
309 		@SuppressWarnings("deprecation")
310 		public int compare(ExecutionFlowDescriptor o1,
311 				ExecutionFlowDescriptor o2) {
312 			// TODO: write unit tests for this
313 
314 			String name1 = o1.getName();
315 			String name2 = o2.getName();
316 
317 			String path1 = o1.getPath();
318 			String path2 = o2.getPath();
319 
320 			// Check whether name include path
321 			int lastIndex1 = name1.lastIndexOf('/');
322 			// log.debug(name1+", "+lastIndex1);
323 			if (!StringUtils.hasText(path1) && lastIndex1 >= 0) {
324 				path1 = name1.substring(0, lastIndex1);
325 				name1 = name1.substring(lastIndex1 + 1);
326 			}
327 
328 			int lastIndex2 = name2.lastIndexOf('/');
329 			if (!StringUtils.hasText(path2) && lastIndex2 >= 0) {
330 				path2 = name2.substring(0, lastIndex2);
331 				name2 = name2.substring(lastIndex2 + 1);
332 			}
333 
334 			// Perform the actual comparison
335 			if (StringUtils.hasText(path1) && StringUtils.hasText(path2)) {
336 				if (path1.equals(path2))
337 					return name1.compareTo(name2);
338 				else if (path1.startsWith(path2))
339 					return -1;
340 				else if (path2.startsWith(path1))
341 					return 1;
342 				else
343 					return path1.compareTo(path2);
344 			} else if (!StringUtils.hasText(path1)
345 					&& StringUtils.hasText(path2)) {
346 				return 1;
347 			} else if (StringUtils.hasText(path1)
348 					&& !StringUtils.hasText(path2)) {
349 				return -1;
350 			} else if (!StringUtils.hasText(path1)
351 					&& !StringUtils.hasText(path2)) {
352 				return name1.compareTo(name2);
353 			} else {
354 				return 0;
355 			}
356 		}
357 
358 	}
359 }