diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/PlaceholderTargetSource.java b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/PlaceholderTargetSource.java index 04e6ac19d..5db0f54d0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/PlaceholderTargetSource.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/scope/util/PlaceholderTargetSource.java @@ -1,446 +1,465 @@ -/* - * Copyright 2006-2007 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.scope.util; - -import java.beans.PropertyEditor; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.aop.TargetSource; -import org.springframework.aop.target.SimpleBeanTargetSource; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.BeansException; -import org.springframework.beans.PropertyEditorRegistrySupport; -import org.springframework.beans.TypeConverter; -import org.springframework.beans.TypeMismatchException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanDefinitionVisitor; -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.support.ManagedSet; -import org.springframework.core.AttributeAccessor; -import org.springframework.core.MethodParameter; -import org.springframework.util.Assert; -import org.springframework.util.StringValueResolver; - -/** - * A {@link TargetSource} that lazily initializes its target, replacing bean - * definition properties dynamically if they are marked as placeholders. String - * values with embedded %{key} patterns will be replaced with the - * corresponding value from the injected context (which must also be a String). - * This includes dynamically locating a bean reference (e.g. - * ref="%{foo}"), and partial replacement of patterns (e.g. - * value="%{foo}-bar-%{spam}"). These replacements work for context - * values that are primitive (String, Long, Integer). You can also replace - * non-primitive values directly by making the whole bean property value into a - * placeholder (e.g. value="%{foo}" where foo is a - * property in the context). - * - * @author Dave Syer - * - */ -public class PlaceholderTargetSource extends SimpleBeanTargetSource implements InitializingBean { - - /** - * Key for placeholders to be replaced from the properties provided. - */ - private static final String PLACEHOLDER_PREFIX = "%{"; - - private static final String PLACEHOLDER_SUFFIX = "}"; - - private ContextFactory contextFactory; - - private String beanName; - - /** - * Public setter for the context factory. Used to construct the context root - * whenever placeholders are replaced in a bean definition. - * - * @param contextFactory the {@link ContextFactory} - */ - public void setContextFactory(ContextFactory contextFactory) { - this.contextFactory = contextFactory; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() { - Assert.notNull(contextFactory, "The ContextFactory must be set."); - beanName = getTargetBeanName() + "#" + contextFactory.getContextId(); - } - - /* - * (non-Javadoc) - * - * @see org.springframework.aop.target.LazyInitTargetSource#getTarget() - */ - @Override - public synchronized Object getTarget() throws BeansException { - - // Object target; - Object target = getTargetFromContext(); - if (target != null) { - return target; - } - - DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) getBeanFactory(); - - final TypeConverter typeConverter = listableBeanFactory.getTypeConverter(); - - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(listableBeanFactory); - beanFactory.copyConfigurationFrom(listableBeanFactory); - - final TypeConverter contextTypeConverter = new TypeConverter() { - @SuppressWarnings("unchecked") - public Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) - throws TypeMismatchException { - Object result = null; - if (value instanceof String) { - String key = (String) value; - if (key.startsWith(PLACEHOLDER_PREFIX) && key.endsWith(PLACEHOLDER_SUFFIX)) { - key = extractKey(key); - result = convertFromContext(key, requiredType); - if (result == null) { - Object property = getPropertyFromContext(key); - // Give the normal type converter a chance by - // reversing to a String - if (property != null) { - property = convertToString(property, typeConverter); - if (property != null) { - value = property; - } - logger.debug(String.format("Bound %%{%s} to String value [%s]", key, result)); - } - else { - throw new IllegalStateException("Cannot bind to placeholder: " + key); - } - } - else { - logger.debug(String.format("Bound %%{%s} to [%s]", key, result)); - } - } - } - else if (requiredType.isAssignableFrom(value.getClass())) { - result = value; - } - else if (requiredType.isAssignableFrom(String.class)) { - result = convertToString(value, typeConverter); - if (result == null) { - logger.debug("Falling back on toString for conversion of : [" + value.getClass() + "]"); - result = value.toString(); - } - } - return result != null ? result : typeConverter.convertIfNecessary(value, requiredType, methodParam); - } - - @SuppressWarnings("unchecked") - public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException { - return convertIfNecessary(value, requiredType, null); - } - }; - beanFactory.setTypeConverter(contextTypeConverter); - - try { - - /* - * Need to use the merged bean definition here, otherwise it gets - * cached and "frozen" in and the "regular" bean definition does not - * come back when getBean() is called later on - */ - String targetBeanName = getTargetBeanName(); - GenericBeanDefinition beanDefinition = new GenericBeanDefinition(listableBeanFactory - .getMergedBeanDefinition(targetBeanName)); - logger.debug("Rehydrating scoped target: [" + targetBeanName + "]"); - - BeanDefinitionVisitor visitor = new PlaceholderBeanDefinitionVisitor(contextTypeConverter); - - beanFactory.registerBeanDefinition(beanName, beanDefinition); - // Make the replacements before the target is hydrated - visitor.visitBeanDefinition(beanDefinition); - target = beanFactory.getBean(beanName); - putTargetInContext(target); - return target; - - } - finally { - beanFactory.removeBeanDefinition(beanName); - beanFactory = null; - // Anything else we can do to clean it up? - } - - } - - private void putTargetInContext(Object target) { - Object context = contextFactory.getContext(); - if (context instanceof AttributeAccessor) { - ((AttributeAccessor) context).setAttribute(beanName, target); - } - } - - private Object getTargetFromContext() { - Object context = contextFactory.getContext(); - if (context instanceof AttributeAccessor) { - return ((AttributeAccessor) context).getAttribute(beanName); - } - return null; - } - - /** - * @param value - * @param typeConverter - * @return a String representation of the input if possible - */ - protected String convertToString(Object value, TypeConverter typeConverter) { - String result = null; - try { - // Give it one chance to convert - this forces the default editors - // to be registered - result = (String) typeConverter.convertIfNecessary(value, String.class); - } - catch (TypeMismatchException e) { - // ignore - } - if (result == null && typeConverter instanceof PropertyEditorRegistrySupport) { - /* - * PropertyEditorRegistrySupport is de rigeur with TypeConverter - * instances used internally by Spring. If we have one of those then - * we can convert to String but the TypeConverter doesn't know how - * to. - */ - PropertyEditorRegistrySupport registry = (PropertyEditorRegistrySupport) typeConverter; - PropertyEditor editor = registry.findCustomEditor(value.getClass(), null); - if (editor != null) { - if (registry.isSharedEditor(editor)) { - // Synchronized access to shared editor - // instance. - synchronized (editor) { - editor.setValue(value); - result = editor.getAsText(); - } - } - else { - editor.setValue(value); - result = editor.getAsText(); - } - } - } - return result; - } - - /** - * @param value - * @param requiredType - * @return - */ - private Object convertFromContext(String key, Class requiredType) { - Object result = null; - Object property = getPropertyFromContext(key); - if (property == null || requiredType.isAssignableFrom(property.getClass())) { - result = property; - } - return result; - } - - private Object getPropertyFromContext(String key) { - Object context = contextFactory.getContext(); - if (context==null) { - throw new IllegalStateException("No context available while replacing placeholders."); - } - BeanWrapper wrapper = new BeanWrapperImpl(context); - if (wrapper.isReadableProperty(key)) { - return wrapper.getPropertyValue(key); - } - return null; - } - - private String extractKey(String value) { - return value.substring(value.indexOf(PLACEHOLDER_PREFIX) + PLACEHOLDER_PREFIX.length(), value - .indexOf(PLACEHOLDER_SUFFIX)); - } - - /** - * Determine whether the input is a whole key in the form - * %{...}, i.e. starting with the correct prefix, ending with - * the correct suffix and containing only one of each. - * - * @param value a String with placeholder patterns - * @return true if the value is a key - */ - private boolean isKey(String value) { - return value.indexOf(PLACEHOLDER_PREFIX) == value.lastIndexOf(PLACEHOLDER_PREFIX) - && value.startsWith(PLACEHOLDER_PREFIX) && value.endsWith(PLACEHOLDER_SUFFIX); - } - - /** - * A {@link BeanDefinitionVisitor} that will replace embedded placeholders - * with values from the provided context. - * - * @author Dave Syer - * - */ - private final class PlaceholderBeanDefinitionVisitor extends BeanDefinitionVisitor { - - public PlaceholderBeanDefinitionVisitor(final TypeConverter typeConverter) { - super(new PlaceholderStringValueResolver(typeConverter)); - } - - @SuppressWarnings("unchecked") - protected Object resolveValue(Object value) { - - if (value instanceof TypedStringValue) { - - TypedStringValue typedStringValue = (TypedStringValue) value; - String stringValue = typedStringValue.getValue(); - if (stringValue != null) { - - // If the value is a whole key, try to simply replace it - // from context. - if (isKey(stringValue)) { - String key = extractKey(stringValue); - Object result = getPropertyFromContext(key); - if (result != null) { - value = result; - logger.debug(String.format("Resolved %%{%s} to obtain [%s]", key, result)); - } - } - else { - // Otherwise it might contain embedded keys so we try to - // replace those - String visitedString = resolveStringValue(stringValue); - value = new TypedStringValue(visitedString); - } - } - - } else if (value instanceof Map) { - - Map map = (Map) value; - Map newValue = new ManagedMap(map.size()); - newValue.putAll(map); - super.visitMap(newValue); - value = newValue; - - } else if (value instanceof List) { - - List list = (List) value; - List newValue = new ManagedList(list.size()); - newValue.addAll(list); - super.visitList(newValue); - value = newValue; - - } else if (value instanceof Set) { - - Set list = (Set) value; - Set newValue = new ManagedSet(list.size()); - newValue.addAll(list); - super.visitSet(newValue); - value = newValue; - - } - else { - - value = super.resolveValue(value); - - } - - return value; - - } - - } - - private final class PlaceholderStringValueResolver implements StringValueResolver { - - private final TypeConverter typeConverter; - - private PlaceholderStringValueResolver(TypeConverter typeConverter) { - this.typeConverter = typeConverter; - } - - public String resolveStringValue(String strVal) { - if (!strVal.contains(PLACEHOLDER_PREFIX)) { - return strVal; - } - return replacePlaceholders(strVal, typeConverter); - } - - /** - * Convenience method to replace all the placeholders in the input. - * - * @param typeConverter a {@link TypeConverter} that can be used to - * convert placeholder keys to context values - * @param value the value to replace placeholders in - * @return the input with placeholders replaced - */ - private String replacePlaceholders(String value, TypeConverter typeConverter) { - - StringBuilder result = new StringBuilder(value); - - int first = result.indexOf(PLACEHOLDER_PREFIX); - int next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1); - - while (first >= 0) { - - Assert.state(next > 0, String.format("Placeholder key incorrectly specified: use %skey%s (in %s)", - PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, value)); - - String key = result.substring(first + PLACEHOLDER_PREFIX.length(), next); - - boolean replaced = replaceIfTypeMatches(result, first, next, key, String.class, typeConverter); - replaced |= replaceIfTypeMatches(result, first, next, key, Long.class, typeConverter); - replaced |= replaceIfTypeMatches(result, first, next, key, Integer.class, typeConverter); - replaced |= replaceIfTypeMatches(result, first, next, key, Date.class, typeConverter); - if (!replaced) { - if (!value.startsWith(PLACEHOLDER_PREFIX) || !value.endsWith(PLACEHOLDER_SUFFIX)) { - throw new IllegalStateException(String.format("Cannot bind to partial key %%{%s} in %s", key, - value)); - } - logger.debug(String.format("Deferring binding of placeholder: %%{%s}", key)); - } - else { - logger.debug(String.format("Bound %%{%s} to obtain [%s]", key, result)); - } - first = result.indexOf(PLACEHOLDER_PREFIX, first + 1); - next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1); - - } - - return result.toString(); - - } - - private boolean replaceIfTypeMatches(StringBuilder result, int first, int next, String key, - Class requiredType, TypeConverter typeConverter) { - Object property = convertFromContext(key, requiredType); - if (property != null) { - result.replace(first, next + 1, (String) typeConverter.convertIfNecessary(property, String.class)); - return true; - } - return false; - } - - } - -} +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.scope.util; + +import java.beans.PropertyEditor; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.target.SimpleBeanTargetSource; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyEditorRegistrySupport; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanDefinitionVisitor; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.support.ManagedSet; +import org.springframework.core.AttributeAccessor; +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.StringValueResolver; + +/** + * A {@link TargetSource} that lazily initializes its target, replacing bean + * definition properties dynamically if they are marked as placeholders. String + * values with embedded %{key} patterns will be replaced with the + * corresponding value from the injected context (which must also be a String). + * This includes dynamically locating a bean reference (e.g. + * ref="%{foo}"), and partial replacement of patterns (e.g. + * value="%{foo}-bar-%{spam}"). These replacements work for context + * values that are primitive (String, Long, Integer). You can also replace + * non-primitive values directly by making the whole bean property value into a + * placeholder (e.g. value="%{foo}" where foo is a + * property in the context). + * + * @author Dave Syer + * + */ +public class PlaceholderTargetSource extends SimpleBeanTargetSource implements InitializingBean { + + /** + * Key for placeholders to be replaced from the properties provided. + */ + private static final String PLACEHOLDER_PREFIX = "%{"; + + private static final String PLACEHOLDER_SUFFIX = "}"; + + private ContextFactory contextFactory; + + private String beanName; + + /** + * Public setter for the context factory. Used to construct the context root + * whenever placeholders are replaced in a bean definition. + * + * @param contextFactory the {@link ContextFactory} + */ + public void setContextFactory(ContextFactory contextFactory) { + this.contextFactory = contextFactory; + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() { + Assert.notNull(contextFactory, "The ContextFactory must be set."); + beanName = getTargetBeanName() + "#" + contextFactory.getContextId(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.aop.target.LazyInitTargetSource#getTarget() + */ + @Override + public synchronized Object getTarget() throws BeansException { + + // Object target; + Object target = getTargetFromContext(); + if (target != null) { + return target; + } + + DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) getBeanFactory(); + + final TypeConverter typeConverter = listableBeanFactory.getTypeConverter(); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(listableBeanFactory); + beanFactory.copyConfigurationFrom(listableBeanFactory); + + final TypeConverter contextTypeConverter = new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) + throws TypeMismatchException { + Object result = null; + if (value instanceof String) { + String key = (String) value; + if (key.startsWith(PLACEHOLDER_PREFIX) && key.endsWith(PLACEHOLDER_SUFFIX)) { + key = extractKey(key); + result = convertFromContext(key, requiredType); + if (result == null) { + Object property = getPropertyFromContext(key); + // Give the normal type converter a chance by + // reversing to a String + if (property != null) { + property = convertToString(property, typeConverter); + if (property != null) { + value = property; + } + logger.debug(String.format("Bound %%{%s} to String value [%s]", key, result)); + } + else { + throw new IllegalStateException("Cannot bind to placeholder: " + key); + } + } + else { + logger.debug(String.format("Bound %%{%s} to [%s]", key, result)); + } + } + } + else if (requiredType.isAssignableFrom(value.getClass())) { + result = value; + } + else if (requiredType.isAssignableFrom(String.class)) { + result = convertToString(value, typeConverter); + if (result == null) { + logger.debug("Falling back on toString for conversion of : [" + value.getClass() + "]"); + result = value.toString(); + } + } + return result != null ? result : typeConverter.convertIfNecessary(value, requiredType, methodParam); + } + + @SuppressWarnings("unchecked") + public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException { + return convertIfNecessary(value, requiredType, null); + } + }; + beanFactory.setTypeConverter(contextTypeConverter); + + try { + + /* + * Need to use the merged bean definition here, otherwise it gets + * cached and "frozen" in and the "regular" bean definition does not + * come back when getBean() is called later on + */ + String targetBeanName = getTargetBeanName(); + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(listableBeanFactory + .getMergedBeanDefinition(targetBeanName)); + logger.debug("Rehydrating scoped target: [" + targetBeanName + "]"); + + BeanDefinitionVisitor visitor = new PlaceholderBeanDefinitionVisitor(contextTypeConverter); + + beanFactory.registerBeanDefinition(beanName, beanDefinition); + // Make the replacements before the target is hydrated + visitor.visitBeanDefinition(beanDefinition); + target = beanFactory.getBean(beanName); + putTargetInContext(target); + return target; + + } + finally { + beanFactory.removeBeanDefinition(beanName); + beanFactory = null; + // Anything else we can do to clean it up? + } + + } + + private void putTargetInContext(Object target) { + Object context = contextFactory.getContext(); + if (context instanceof AttributeAccessor) { + ((AttributeAccessor) context).setAttribute(beanName, target); + } + } + + private Object getTargetFromContext() { + Object context = contextFactory.getContext(); + if (context instanceof AttributeAccessor) { + return ((AttributeAccessor) context).getAttribute(beanName); + } + return null; + } + + /** + * @param value + * @param typeConverter + * @return a String representation of the input if possible + */ + protected String convertToString(Object value, TypeConverter typeConverter) { + String result = null; + try { + // Give it one chance to convert - this forces the default editors + // to be registered + result = (String) typeConverter.convertIfNecessary(value, String.class); + } + catch (TypeMismatchException e) { + // ignore + } + if (result == null && typeConverter instanceof PropertyEditorRegistrySupport) { + /* + * PropertyEditorRegistrySupport is de rigeur with TypeConverter + * instances used internally by Spring. If we have one of those then + * we can convert to String but the TypeConverter doesn't know how + * to. + */ + PropertyEditorRegistrySupport registry = (PropertyEditorRegistrySupport) typeConverter; + PropertyEditor editor = registry.findCustomEditor(value.getClass(), null); + if (editor != null) { + if (registry.isSharedEditor(editor)) { + // Synchronized access to shared editor + // instance. + synchronized (editor) { + editor.setValue(value); + result = editor.getAsText(); + } + } + else { + editor.setValue(value); + result = editor.getAsText(); + } + } + } + return result; + } + + /** + * @param value + * @param requiredType + * @return + */ + private Object convertFromContext(String key, Class requiredType) { + Object result = null; + Object property = getPropertyFromContext(key); + if (property == null || requiredType.isAssignableFrom(property.getClass())) { + result = property; + } + return result; + } + + private Object getPropertyFromContext(String key) { + Object context = contextFactory.getContext(); + if (context == null) { + throw new IllegalStateException("No context available while replacing placeholders."); + } + BeanWrapper wrapper = new BeanWrapperImpl(context); + if (wrapper.isReadableProperty(key)) { + return wrapper.getPropertyValue(key); + } + return null; + } + + private String extractKey(String value) { + return value.substring(value.indexOf(PLACEHOLDER_PREFIX) + PLACEHOLDER_PREFIX.length(), value + .indexOf(PLACEHOLDER_SUFFIX)); + } + + /** + * Determine whether the input is a whole key in the form + * %{...}, i.e. starting with the correct prefix, ending with + * the correct suffix and containing only one of each. + * + * @param value a String with placeholder patterns + * @return true if the value is a key + */ + private boolean isKey(String value) { + return value.indexOf(PLACEHOLDER_PREFIX) == value.lastIndexOf(PLACEHOLDER_PREFIX) + && value.startsWith(PLACEHOLDER_PREFIX) && value.endsWith(PLACEHOLDER_SUFFIX); + } + + /** + * A {@link BeanDefinitionVisitor} that will replace embedded placeholders + * with values from the provided context. + * + * @author Dave Syer + * + */ + private final class PlaceholderBeanDefinitionVisitor extends BeanDefinitionVisitor { + + public PlaceholderBeanDefinitionVisitor(final TypeConverter typeConverter) { + super(new PlaceholderStringValueResolver(typeConverter)); + } + + @SuppressWarnings("unchecked") + protected Object resolveValue(Object value) { + + if (value instanceof TypedStringValue) { + + TypedStringValue typedStringValue = (TypedStringValue) value; + String stringValue = typedStringValue.getValue(); + if (stringValue != null) { + + // If the value is a whole key, try to simply replace it + // from context. + if (isKey(stringValue)) { + String key = extractKey(stringValue); + Object result = getPropertyFromContext(key); + if (result != null) { + value = result; + logger.debug(String.format("Resolved %%{%s} to obtain [%s]", key, result)); + } + } + else { + // Otherwise it might contain embedded keys so we try to + // replace those + String visitedString = resolveStringValue(stringValue); + value = new TypedStringValue(visitedString); + } + } + + } + else if (value instanceof Map) { + + Map map = (Map) value; + Map newValue = new ManagedMap(map.size()); + newValue.putAll(map); + super.visitMap(newValue); + value = newValue; + + } + else if (value instanceof List) { + + List list = (List) value; + List newValue = new ManagedList(list.size()); + newValue.addAll(list); + super.visitList(newValue); + value = newValue; + + } + else if (value instanceof Set) { + + Set list = (Set) value; + Set newValue = new ManagedSet(list.size()); + newValue.addAll(list); + super.visitSet(newValue); + value = newValue; + + } + else if (value instanceof BeanDefinition) { + + BeanDefinition newValue = new GenericBeanDefinition((BeanDefinition) value); + visitBeanDefinition((BeanDefinition) newValue); + value = newValue; + + } + else if (value instanceof BeanDefinitionHolder) { + + BeanDefinition newValue = new GenericBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); + visitBeanDefinition((BeanDefinition) newValue); + value = newValue; + + } + else { + + value = super.resolveValue(value); + + } + + return value; + + } + + } + + private final class PlaceholderStringValueResolver implements StringValueResolver { + + private final TypeConverter typeConverter; + + private PlaceholderStringValueResolver(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + public String resolveStringValue(String strVal) { + if (!strVal.contains(PLACEHOLDER_PREFIX)) { + return strVal; + } + return replacePlaceholders(strVal, typeConverter); + } + + /** + * Convenience method to replace all the placeholders in the input. + * + * @param typeConverter a {@link TypeConverter} that can be used to + * convert placeholder keys to context values + * @param value the value to replace placeholders in + * @return the input with placeholders replaced + */ + private String replacePlaceholders(String value, TypeConverter typeConverter) { + + StringBuilder result = new StringBuilder(value); + + int first = result.indexOf(PLACEHOLDER_PREFIX); + int next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1); + + while (first >= 0) { + + Assert.state(next > 0, String.format("Placeholder key incorrectly specified: use %skey%s (in %s)", + PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, value)); + + String key = result.substring(first + PLACEHOLDER_PREFIX.length(), next); + + boolean replaced = replaceIfTypeMatches(result, first, next, key, String.class, typeConverter); + replaced |= replaceIfTypeMatches(result, first, next, key, Long.class, typeConverter); + replaced |= replaceIfTypeMatches(result, first, next, key, Integer.class, typeConverter); + replaced |= replaceIfTypeMatches(result, first, next, key, Date.class, typeConverter); + if (!replaced) { + if (!value.startsWith(PLACEHOLDER_PREFIX) || !value.endsWith(PLACEHOLDER_SUFFIX)) { + throw new IllegalStateException(String.format("Cannot bind to partial key %%{%s} in %s", key, + value)); + } + logger.debug(String.format("Deferring binding of placeholder: %%{%s}", key)); + } + else { + logger.debug(String.format("Bound %%{%s} to obtain [%s]", key, result)); + } + first = result.indexOf(PLACEHOLDER_PREFIX, first + 1); + next = result.indexOf(PLACEHOLDER_SUFFIX, first + 1); + + } + + return result.toString(); + + } + + private boolean replaceIfTypeMatches(StringBuilder result, int first, int next, String key, + Class requiredType, TypeConverter typeConverter) { + Object property = convertFromContext(key, requiredType); + if (property != null) { + result.replace(first, next + 1, (String) typeConverter.convertIfNecessary(property, String.class)); + return true; + } + return false; + } + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java index 851a164eb..bd710336a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/partition/support/TaskExecutorPartitionHandlerTests.java @@ -7,6 +7,7 @@ import static org.junit.Assert.fail; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.TreeSet; import org.junit.Before; import org.junit.Test; @@ -26,6 +27,8 @@ public class TaskExecutorPartitionHandlerTests { private TaskExecutorPartitionHandler handler = new TaskExecutorPartitionHandler(); private int count = 0; + + private Collection stepExecutions = new TreeSet(); private StepExecution stepExecution = new StepExecution("step", new JobExecution(1L)); @@ -50,6 +53,7 @@ public class TaskExecutorPartitionHandlerTests { @Override public void execute(StepExecution stepExecution) throws JobInterruptedException { count++; + stepExecutions.add(stepExecution.getStepName()); } }); handler.afterPropertiesSet(); @@ -74,6 +78,7 @@ public class TaskExecutorPartitionHandlerTests { handler.setGridSize(2); handler.handle(stepExecutionSplitter, stepExecution); assertEquals(2, count); + assertEquals("[foo0, foo1]", stepExecutions.toString()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests.java index dc9f669b5..e6c3ece20 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests.java @@ -1,196 +1,227 @@ -/* - * Copyright 2006-2007 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.scope.util; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Dave Syer - * - */ -@ContextConfiguration -@RunWith(SpringJUnit4ClassRunner.class) -public class MultipleContextPlaceholderTargetSourceTests { - - private Map attributes; - - public Map getAttributes() { - return attributes; - } - - @Autowired - private SimpleContextFactory contextFactory; - - @Autowired - @Qualifier("simple") - private TestBean simple; - - @Autowired - @Qualifier("nested") - private TestBean nested; - - @Autowired - @Qualifier("list") - private TestBean list; - - @Autowired - @Qualifier("map") - private TestBean map; - - @After - public void removeContext() { - contextFactory.clearContext(); - } - - @Before - public void setUpContext() { - contextFactory.setContext(this); - } - - @Test - public void testValueFromProperties() throws Exception { - attributes = Collections.singletonMap("foo", "bar"); - assertEquals("bar", simple.getName()); - } - - @Test - public void testMultipleValueFromProperties() throws Exception { - - for (int i = 0; i < 4; i++) { - final String value = "foo" + i; - attributes = Collections.singletonMap("foo", value); - assertEquals("foo" + i, simple.getName()); - } - - } - - @Test - public void testMultipleNestedValueFromProperties() throws Exception { - - for (int i = 0; i < 4; i++) { - final String value = "bar" + i; - attributes = Collections.singletonMap("foo", value); - assertEquals("foo-bar" + i, nested.getName()); - } - - } - - @Test - public void testMultipleValueInList() throws Exception { - - for (int i = 0; i < 4; i++) { - final String value = "foo" + i; - contextFactory.setContext(this); - attributes = Collections.singletonMap("foo", value); - try { - assertEquals("foo" + i, list.getNames().get(0)); - } - finally { - contextFactory.clearContext(); - } - } - - } - - @Test - public void testMultipleValueInMap() throws Exception { - - for (int i = 0; i < 4; i++) { - final String value = "foo" + i; - contextFactory.setContext(this); - attributes = Collections.singletonMap("foo", value); - try { - assertEquals("foo" + i, map.getMap().get("foo")); - } - finally { - contextFactory.clearContext(); - } - } - - } - - @Override - public String toString() { - return "Test context: attributes=" + attributes; - } - - public static class TestBean { - private String name; - - private List names = new ArrayList(); - - private Map map = new HashMap(); - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getNames() { - return new ArrayList(names); - } - - public void setNames(List names) { - this.names.addAll(names); - } - - public Map getMap() { - return new HashMap(map); - } - - public void setMap(Map map) { - this.map.putAll(map); - } - - } - - public static class SimpleContextFactory extends ContextFactorySupport { - - private Object root; - - public Object getContext() { - return root; - } - - public void setContext(Object root) { - this.root = root; - } - - public void clearContext() { - root = null; - } - - } - -} +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.scope.util; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dave Syer + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class MultipleContextPlaceholderTargetSourceTests { + + private Map attributes; + + public Map getAttributes() { + return attributes; + } + + @Autowired + private SimpleContextFactory contextFactory; + + @Autowired + @Qualifier("simple") + private TestBean simple; + + @Autowired + @Qualifier("nested") + private TestBean nested; + + @Autowired + @Qualifier("list") + private TestBean list; + + @Autowired + @Qualifier("nestedList") + private TestBean nestedList; + + @Autowired + @Qualifier("map") + private TestBean map; + + @After + public void removeContext() { + contextFactory.clearContext(); + } + + @Before + public void setUpContext() { + contextFactory.setContext(this); + } + + @Test + public void testValueFromProperties() throws Exception { + attributes = Collections.singletonMap("foo", "bar"); + assertEquals("bar", simple.getName()); + } + + @Test + public void testMultipleValueFromProperties() throws Exception { + + for (int i = 0; i < 4; i++) { + final String value = "foo" + i; + attributes = Collections.singletonMap("foo", value); + assertEquals("foo" + i, simple.getName()); + } + + } + + @Test + public void testMultipleNestedValueFromProperties() throws Exception { + + for (int i = 0; i < 4; i++) { + final String value = "bar" + i; + attributes = Collections.singletonMap("foo", value); + assertEquals("foo-bar" + i, nested.getName()); + } + + } + + @Test + public void testMultipleValueInList() throws Exception { + + for (int i = 0; i < 4; i++) { + final String value = "foo" + i; + contextFactory.setContext(this); + attributes = Collections.singletonMap("foo", value); + try { + assertEquals("foo" + i, list.getNames().get(0)); + } + finally { + contextFactory.clearContext(); + } + } + + } + + @Test + public void testMultipleValueInNestedList() throws Exception { + + for (int i = 0; i < 4; i++) { + final String value = "foo" + i; + contextFactory.setContext(this); + attributes = Collections.singletonMap("foo", value); + try { + assertEquals("foo" + i, nestedList.getParent().getNames().get(0)); + } + finally { + contextFactory.clearContext(); + } + } + + } + + @Test + public void testMultipleValueInMap() throws Exception { + + for (int i = 0; i < 4; i++) { + final String value = "foo" + i; + contextFactory.setContext(this); + attributes = Collections.singletonMap("foo", value); + try { + assertEquals("foo" + i, map.getMap().get("foo")); + } + finally { + contextFactory.clearContext(); + } + } + + } + + @Override + public String toString() { + return "Test context: attributes=" + attributes; + } + + public static class TestBean { + private String name; + + private TestBean parent; + + private List names = new ArrayList(); + + private Map map = new HashMap(); + + public TestBean getParent() { + return parent; + } + + public void setParent(TestBean parent) { + this.parent = parent; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getNames() { + return new ArrayList(names); + } + + public void setNames(List names) { + this.names.addAll(names); + } + + public Map getMap() { + return new HashMap(map); + } + + public void setMap(Map map) { + this.map.putAll(map); + } + + } + + public static class SimpleContextFactory extends ContextFactorySupport { + + private Object root; + + public Object getContext() { + return root; + } + + public void setContext(Object root) { + this.root = root; + } + + public void clearContext() { + root = null; + } + + } + +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests-context.xml index 672f3c01d..b106e2f00 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/util/MultipleContextPlaceholderTargetSourceTests-context.xml @@ -1,79 +1,104 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %{attributes[foo]} - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %{attributes[foo]} + + + + + + + + + + %{attributes[foo]} + + + + + + + + + + + + + + + + \ No newline at end of file