+/*\r
+ * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
package org.argeo.slc.core.execution;\r
\r
import java.beans.PropertyDescriptor;\r
import java.util.Iterator;\r
import java.util.List;\r
import java.util.Map;\r
-import java.util.Properties;\r
import java.util.Set;\r
\r
import org.apache.commons.logging.Log;\r
import org.apache.commons.logging.LogFactory;\r
import org.argeo.slc.SlcException;\r
import org.argeo.slc.execution.ExecutionContext;\r
+import org.argeo.slc.execution.ExecutionFlow;\r
import org.springframework.beans.BeansException;\r
import org.springframework.beans.MutablePropertyValues;\r
import org.springframework.beans.PropertyValue;\r
import org.springframework.beans.PropertyValues;\r
+import org.springframework.beans.factory.BeanDefinitionStoreException;\r
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;\r
-import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;\r
import org.springframework.beans.factory.config.TypedStringValue;\r
import org.springframework.beans.factory.support.ManagedList;\r
import org.springframework.beans.factory.support.ManagedMap;\r
import org.springframework.beans.factory.support.ManagedSet;\r
import org.springframework.util.ObjectUtils;\r
+import org.springframework.util.StringUtils;\r
\r
+/**\r
+ * Spring post processor which ensures that execution parameters are properly\r
+ * set. It is used at two levels: first during instantiation for instantiation\r
+ * parameters which allow to implement templates, then at runtime in order to\r
+ * interpret @{} placeholders when object of scope execution are instantiated.\r
+ */\r
public class ExecutionParameterPostProcessor extends\r
InstantiationAwareBeanPostProcessorAdapter {\r
\r
private final static Log log = LogFactory\r
.getLog(ExecutionParameterPostProcessor.class);\r
- \r
- private ExecutionContext executionContext;\r
-\r
- private ExecutionScope executionScope;\r
\r
+ private ExecutionContext executionContext;\r
private InstantiationManager instantiationManager;\r
- \r
- public InstantiationManager getInstantiationManager() {\r
- return instantiationManager;\r
- }\r
-\r
- public void setInstantiationManager(\r
- InstantiationManager instantiationManager) {\r
- this.instantiationManager = instantiationManager;\r
- }\r
-\r
- public void setExecutionScope(ExecutionScope executionScope) {\r
- this.executionScope = executionScope;\r
- }\r
-\r
- public ExecutionContext getExecutionContext() {\r
- return executionContext;\r
- }\r
-\r
- public void setExecutionContext(ExecutionContext executionContext) {\r
- this.executionContext = executionContext;\r
- }\r
\r
private String placeholderPrefix = "@{";\r
private String placeholderSuffix = "}";\r
PropertyDescriptor[] pds, Object bean, String beanName)\r
throws BeansException {\r
\r
- //TODO: resolve at execution only if scope is execution\r
- //TODO: deal with placeholders in RuntimeBeanReference and RuntimeBeanNameReference\r
- \r
+ // TODO: resolve at execution only if scope is execution\r
+ // TODO: deal with placeholders in RuntimeBeanReference and\r
+ // RuntimeBeanNameReference\r
+\r
MutablePropertyValues newPvs = new MutablePropertyValues();\r
- \r
+\r
boolean changesOccured = false;\r
- \r
- CustomPpc ppc = new CustomPpc(beanName);\r
- \r
- for(PropertyValue pv : pvs.getPropertyValues()) {\r
- Object convertedValue = ppc.resolveValue(pv.getValue());\r
+\r
+ for (PropertyValue pv : pvs.getPropertyValues()) {\r
+ Object convertedValue = resolveValue(beanName, bean, pv.getValue());\r
newPvs.addPropertyValue(new PropertyValue(pv, convertedValue));\r
- if(convertedValue != pv.getValue()) {\r
+ if (convertedValue != pv.getValue()) {\r
changesOccured = true;\r
}\r
}\r
- \r
+\r
return changesOccured ? newPvs : pvs;\r
}\r
\r
+ @Override\r
+ public boolean postProcessAfterInstantiation(Object bean, String beanName)\r
+ throws BeansException {\r
+ if (bean instanceof ExecutionFlow)\r
+ instantiationManager.flowInitializationStarted(\r
+ (ExecutionFlow) bean, beanName);\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public Object postProcessAfterInitialization(Object bean, String beanName)\r
+ throws BeansException {\r
+ if (bean instanceof ExecutionFlow)\r
+ instantiationManager.flowInitializationFinished(\r
+ (ExecutionFlow) bean, beanName);\r
+ return bean;\r
+ }\r
+\r
+ protected String resolvePlaceholder(Object bean, String placeholder) {\r
+ if (instantiationManager.isInFlowInitialization())\r
+ return instantiationManager.getInitializingFlowParameter(\r
+ placeholder).toString();\r
+\r
+ else {// execution\r
+ // next call fail if no execution context available\r
+ Object obj = executionContext.getVariable(placeholder);\r
+ if (obj != null) {\r
+ return obj.toString();\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ @SuppressWarnings(value = { "unchecked" })\r
+ public Object resolveValue(String beanName, Object bean, Object value) {\r
+ if (value instanceof TypedStringValue) {\r
+ TypedStringValue tsv = (TypedStringValue) value;\r
+ String originalValue = tsv.getValue();\r
+\r
+ String convertedValue = resolveString(beanName, bean, originalValue);\r
+ if (convertedValue == null)\r
+ return null;\r
+ return convertedValue.equals(originalValue) ? value\r
+ : new TypedStringValue(convertedValue);\r
+ } else if (value instanceof String) {\r
+ String originalValue = value.toString();\r
+ String convertedValue = resolveString(beanName, bean, originalValue);\r
+ if (convertedValue == null)\r
+ return null;\r
+ return convertedValue.equals(originalValue) ? value\r
+ : convertedValue;\r
+ } else if (value instanceof ManagedMap) {\r
+ Map<?, ?> mapVal = (Map<?, ?>) value;\r
+\r
+ Map<Object, Object> newContent = new ManagedMap();\r
+ boolean entriesModified = false;\r
+ for (Iterator<?> it = mapVal.entrySet().iterator(); it.hasNext();) {\r
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();\r
+ Object key = entry.getKey();\r
+ int keyHash = (key != null ? key.hashCode() : 0);\r
+ Object newKey = resolveValue(beanName, bean, key);\r
+ int newKeyHash = (newKey != null ? newKey.hashCode() : 0);\r
+ Object val = entry.getValue();\r
+ Object newVal = resolveValue(beanName, bean, val);\r
+ newContent.put(newKey, newVal);\r
+ entriesModified = entriesModified\r
+ || (newVal != val || newKey != key || newKeyHash != keyHash);\r
+ }\r
+\r
+ return entriesModified ? newContent : value;\r
+ } else if (value instanceof ManagedList) {\r
+ List<?> listVal = (List<?>) value;\r
+ List<Object> newContent = new ManagedList();\r
+ boolean valueModified = false;\r
+\r
+ for (int i = 0; i < listVal.size(); i++) {\r
+ Object elem = listVal.get(i);\r
+ Object newVal = resolveValue(beanName, bean, elem);\r
+ newContent.add(newVal);\r
+ if (!ObjectUtils.nullSafeEquals(newVal, elem)) {\r
+ valueModified = true;\r
+ }\r
+ }\r
+ return valueModified ? newContent : value;\r
+ } else if (value instanceof ManagedSet) {\r
+ Set<?> setVal = (Set<?>) value;\r
+ Set<Object> newContent = new ManagedSet();\r
+ boolean entriesModified = false;\r
+ for (Iterator<?> it = setVal.iterator(); it.hasNext();) {\r
+ Object elem = it.next();\r
+ int elemHash = (elem != null ? elem.hashCode() : 0);\r
+ Object newVal = resolveValue(beanName, bean, elem);\r
+ int newValHash = (newVal != null ? newVal.hashCode() : 0);\r
+ newContent.add(newVal);\r
+ entriesModified = entriesModified\r
+ || (newVal != elem || newValHash != elemHash);\r
+ }\r
+ return entriesModified ? newContent : value;\r
+ } else {\r
+ // log.debug(beanName + ": " + value.getClass() + " : " + value);\r
+ return value;\r
+ }\r
+\r
+ }\r
+\r
+ private String resolveString(String beanName, Object bean, String strVal) {\r
+ // in case <null/> is passed\r
+ if (strVal == null)\r
+ return null;\r
+\r
+ String value = parseStringValue(bean, strVal, new HashSet<String>());\r
+\r
+ if (value == null)\r
+ throw new SlcException("Could not resolve placeholder '" + strVal\r
+ + "' in bean '" + beanName + "'");\r
+\r
+ return (value.equals(nullValue) ? null : value);\r
+ }\r
+\r
public void setPlaceholderPrefix(String placeholderPrefix) {\r
this.placeholderPrefix = placeholderPrefix;\r
}\r
public void setNullValue(String nullValue) {\r
this.nullValue = nullValue;\r
}\r
- \r
- private class CustomPpc extends PropertyPlaceholderConfigurer {\r
- private final Properties props;\r
- String beanName;\r
- \r
- public CustomPpc(String beanName) {\r
- super();\r
- this.props = new Properties();\r
- this.beanName = beanName;\r
- setPlaceholderPrefix(placeholderPrefix);\r
- setPlaceholderSuffix(placeholderSuffix);\r
- setSystemPropertiesMode(SYSTEM_PROPERTIES_MODE_NEVER);\r
- }\r
\r
- /** Public access to the internals of PropertyPlaceholderConfigurer*/\r
- public String resolveString(String strVal) {\r
- String value = parseStringValue(strVal, this.props,\r
- new HashSet<String>());\r
- return (value.equals(nullValue) ? null : value);\r
- }\r
- \r
- public Object resolveValue(Object value) {\r
- if (value instanceof TypedStringValue) {\r
- TypedStringValue tsv = (TypedStringValue) value;\r
- String originalValue = tsv.getValue();\r
-\r
- String convertedValue = resolveString(originalValue);\r
- return convertedValue.equals(originalValue) ? value : new TypedStringValue(convertedValue);\r
- }\r
- else if (value instanceof String) {\r
- String originalValue = value.toString(); \r
- String convertedValue = resolveString(originalValue);\r
- return convertedValue.equals(originalValue) ? value : convertedValue;\r
- } \r
- else if (value instanceof ManagedMap) {\r
- Map mapVal = (Map) value;\r
- \r
- Map newContent = new ManagedMap();\r
- boolean entriesModified = false;\r
- for (Iterator it = mapVal.entrySet().iterator(); it.hasNext();) {\r
- Map.Entry entry = (Map.Entry) it.next();\r
- Object key = entry.getKey();\r
- int keyHash = (key != null ? key.hashCode() : 0);\r
- Object newKey = resolveValue(key);\r
- int newKeyHash = (newKey != null ? newKey.hashCode() : 0);\r
- Object val = entry.getValue();\r
- Object newVal = resolveValue(val);\r
- newContent.put(newKey, newVal);\r
- entriesModified = entriesModified || (newVal != val || newKey != key || newKeyHash != keyHash);\r
+ public void setInstantiationManager(\r
+ InstantiationManager instantiationManager) {\r
+ this.instantiationManager = instantiationManager;\r
+ }\r
+\r
+ public void setExecutionContext(ExecutionContext executionContext) {\r
+ this.executionContext = executionContext;\r
+ }\r
+\r
+ //\r
+ // Following methods hacked from the internals of\r
+ // PropertyPlaceholderConfigurer\r
+ //\r
+\r
+ protected String parseStringValue(Object bean, String strVal,\r
+ Set<String> visitedPlaceholders)\r
+ throws BeanDefinitionStoreException {\r
+\r
+ // in case <null/> is passed\r
+ if (strVal == null)\r
+ return null;\r
+\r
+ StringBuffer buf = new StringBuffer(strVal);\r
+\r
+ int startIndex = strVal.indexOf(placeholderPrefix);\r
+ while (startIndex != -1) {\r
+ int endIndex = findPlaceholderEndIndex(buf, startIndex);\r
+ if (endIndex != -1) {\r
+ String placeholder = buf.substring(startIndex\r
+ + placeholderPrefix.length(), endIndex);\r
+ if (!visitedPlaceholders.add(placeholder)) {\r
+ throw new BeanDefinitionStoreException(\r
+ "Circular placeholder reference '" + placeholder\r
+ + "' in property definitions");\r
}\r
- \r
- return entriesModified ? newContent : value;\r
- }\r
- else if (value instanceof ManagedList) {\r
- List listVal = (List) value;\r
- List newContent = new ManagedList();\r
- boolean valueModified = false;\r
- \r
- for (int i = 0; i < listVal.size(); i++) {\r
- Object elem = listVal.get(i);\r
- Object newVal = resolveValue(elem);\r
- newContent.add(newVal);\r
- if (!ObjectUtils.nullSafeEquals(newVal, elem)) {\r
- valueModified = true;\r
+ // Recursive invocation, parsing placeholders contained in\r
+ // the placeholder key.\r
+ placeholder = parseStringValue(bean, placeholder,\r
+ visitedPlaceholders);\r
+ // Now obtain the value for the fully resolved key...\r
+ String propVal = resolvePlaceholder(bean, placeholder);\r
+ if (propVal != null) {\r
+ // Recursive invocation, parsing placeholders contained\r
+ // in the\r
+ // previously resolved placeholder value.\r
+ propVal = parseStringValue(bean, propVal,\r
+ visitedPlaceholders);\r
+ buf.replace(startIndex,\r
+ endIndex + placeholderSuffix.length(), propVal);\r
+ if (log.isTraceEnabled()) {\r
+ log.trace("Resolved placeholder '" + placeholder + "'");\r
}\r
- } \r
- return valueModified ? newContent : value;\r
- }\r
- else if (value instanceof ManagedSet) {\r
- Set setVal = (Set) value;\r
- Set newContent = new ManagedSet();\r
- boolean entriesModified = false;\r
- for (Iterator it = setVal.iterator(); it.hasNext();) {\r
- Object elem = it.next();\r
- int elemHash = (elem != null ? elem.hashCode() : 0);\r
- Object newVal = resolveValue(elem);\r
- int newValHash = (newVal != null ? newVal.hashCode() : 0);\r
- newContent.add(newVal);\r
- entriesModified = entriesModified || (newVal != elem || newValHash != elemHash);\r
- } \r
- return entriesModified ? newContent : value;\r
+ startIndex = buf.indexOf(placeholderPrefix, startIndex\r
+ + propVal.length());\r
+ } else {\r
+ throw new BeanDefinitionStoreException(\r
+ "Could not resolve placeholder '" + placeholder\r
+ + "'");\r
+ }\r
+ visitedPlaceholders.remove(placeholder);\r
+ } else {\r
+ startIndex = -1;\r
}\r
- else {\r
- return value;\r
- } \r
}\r
\r
- @Override\r
- protected String resolvePlaceholder(String placeholder, Properties props) { \r
- if ((executionScope != null)\r
- && (executionScope.hasExecutionContext())) {\r
- Object obj = executionContext.findVariable(placeholder);\r
- if(obj != null) {\r
- return obj.toString();\r
+ return buf.toString();\r
+ }\r
+\r
+ private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {\r
+ int index = startIndex + placeholderPrefix.length();\r
+ int withinNestedPlaceholder = 0;\r
+ while (index < buf.length()) {\r
+ if (StringUtils.substringMatch(buf, index, placeholderSuffix)) {\r
+ if (withinNestedPlaceholder > 0) {\r
+ withinNestedPlaceholder--;\r
+ index = index + placeholderSuffix.length();\r
+ } else {\r
+ return index;\r
}\r
+ } else if (StringUtils\r
+ .substringMatch(buf, index, placeholderPrefix)) {\r
+ withinNestedPlaceholder++;\r
+ index = index + placeholderPrefix.length();\r
+ } else {\r
+ index++;\r
}\r
- if (instantiationManager.isInFlowInitialization())\r
- return instantiationManager.getInitializingFlowParameter(\r
- placeholder).toString();\r
- else\r
- throw new SlcException("Could not resolve placeholder '" \r
- + placeholder + "' in bean '" + beanName + "'");\r
}\r
+ return -1;\r
}\r
+\r
}\r