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 }