BATCH-1949: Updated for autoproxy check

This commit is contained in:
Michael Minella
2013-01-11 15:01:51 -06:00
parent fcede7245e
commit aadc5ee39e
13 changed files with 16 additions and 1843 deletions

View File

@@ -80,6 +80,8 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean autoProxy = true;
private final Object mutex = new Object();
/**
@@ -115,6 +117,16 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
this.proxyTargetClass = proxyTargetClass;
}
/**
* Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the
* bean definition (e.g. in an @Configuration class).
*
* @param autoProxy the flag value to set (default true)
*/
public void setAutoProxy(boolean autoProxy) {
this.autoProxy = autoProxy;
}
/**
* This will be used to resolve expressions in step-scoped beans.
*/
@@ -212,6 +224,10 @@ public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered {
beanFactory.registerScope(name, this);
if(!autoProxy) {
return;
}
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so StepScope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

View File

@@ -1,38 +0,0 @@
/*
* 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;
/**
* Interface to allow the context root for placeholder resolution to be switched
* at runtime. Useful for testing.
*
* @author Dave Syer
*
*/
public interface ContextFactory {
/**
* @return a root object to which placeholders resolve relatively
*/
Object getContext();
/**
* @return a unique identifier for this context
*/
String getContextId();
}

View File

@@ -1,190 +0,0 @@
/*
* Copyright 2006-2013 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.lang.reflect.Modifier;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.DefaultScopedObject;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.ClassUtils;
/**
* Factory bean for proxies that can replace placeholders in their target. Just
* a specialisation of {@link ScopedProxyFactoryBean}, with a different target
* source type.
*
* @author Dave Syer
*
*/
@SuppressWarnings({ "serial", "rawtypes" })
public class PlaceholderProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanFactoryAware {
/** The TargetSource that manages scoping */
private final PlaceholderTargetSource scopedTargetSource = new PlaceholderTargetSource();
/** The name of the target bean */
private String targetBeanName;
/** The cached singleton proxy */
private Object proxy;
private final ContextFactory contextFactory;
/**
* Create a new FactoryBean instance.
*/
public PlaceholderProxyFactoryBean(ContextFactory contextFactory) {
this.contextFactory = contextFactory;
// setProxyTargetClass(false);
}
/**
* Set the name of the bean that is to be scoped.
*/
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
this.scopedTargetSource.setTargetBeanName(targetBeanName);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
this.scopedTargetSource.setContextFactory(contextFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName
+ "': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped
// proxy itself is not subject to auto-proxying! Only its target bean
// is.
pf.addInterface(AopInfrastructureBean.class);
this.scopedTargetSource.afterPropertiesSet();
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
@Override
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
@Override
public Class<?> getObjectType() {
if (this.proxy != null) {
return this.proxy.getClass();
}
if (this.scopedTargetSource != null) {
return this.scopedTargetSource.getTargetClass();
}
return null;
}
@Override
public boolean isSingleton() {
return true;
}
/**
* Convenience method to create a {@link BeanDefinition} for a target
* wrapped in a placeholder tarrget source, able to defer binding of
* placeholders until the bean is used.
*
* @param definition a target bean definition
* @param registry a {@link BeanDefinitionRegistry}
* @param proxyTargetClass true if we need to use CGlib to create the
* proxies
* @return a {@link BeanDefinitionHolder} for a
* {@link PlaceholderProxyFactoryBean}
*/
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
// Create a proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(PlaceholderProxyFactoryBean.class);
proxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(new StepContextFactory());
proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition());
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String targetBeanName = "lazyBindingProxy." + originalBeanName;
proxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ProxyFactoryBean's "proxyTargetClass" default is FALSE, so we
// need to set it explicitly here.
proxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.TRUE);
}
else {
proxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
}
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
// The target bean should be ignored in favor of the proxy.
targetDefinition.setAutowireCandidate(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
}

View File

@@ -1,494 +0,0 @@
/*
* 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.lang.reflect.Field;
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 <code>%{key}</code> 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.
* <code>ref="%{foo}"</code>), and partial replacement of patterns (e.g.
* <code>value="%{foo}-bar-%{spam}"</code>). 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. <code>value="%{foo}"</code> where <code>foo</code> 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()
*/
@Override
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() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam)
throws TypeMismatchException {
return doConvertIfNecessary(value, requiredType,
methodParam);
}
private Object doConvertIfNecessary(Object value,
Class requiredType, Object param) {
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();
}
}
if(result != null) {
return result;
}
else if(param instanceof Field) {
return typeConverter.convertIfNecessary(value, requiredType, (Field) param);
}
else {
return typeConverter.convertIfNecessary(value, requiredType, (MethodParameter) param);
}
}
@Override
@SuppressWarnings("rawtypes")
public Object convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException {
return doConvertIfNecessary(value, requiredType, null);
}
@Override
public Object convertIfNecessary(Object value,
Class requiredType, Field field)
throws TypeMismatchException {
return doConvertIfNecessary(value, requiredType,
field);
}
};
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 = 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
* <code>%{...}</code>, 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));
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
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(newValue);
value = newValue;
}
else if (value instanceof BeanDefinitionHolder) {
BeanDefinition newValue = new GenericBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
visitBeanDefinition(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;
}
@Override
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, typeConverter.convertIfNecessary(property, String.class));
return true;
}
return false;
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2006-2013 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 org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
/**
* Implementation of {@link ContextFactory} that provides the current
* {@link StepContext} as a context object.
*
* @author Dave Syer
*
*/
public class StepContextFactory implements ContextFactory {
@Override
public Object getContext() {
return StepSynchronizationManager.getContext();
}
@Override
public String getContextId() {
StepContext context = StepSynchronizationManager.getContext();
return context!=null ? (String) context.getId() : "sysinit";
}
}

View File

@@ -1,147 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class AsyncPlaceholderTargetSourceTests implements BeanFactoryAware {
private ThreadLocal<Map<String, String>> attributes = new ThreadLocal<Map<String, String>>();
public Map<String, String> getAttributes() {
return attributes.get();
}
@Autowired
private Node simple;
@Autowired
private SimpleContextFactory contextFactory;
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
private ListableBeanFactory beanFactory;
private int beanCount;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ListableBeanFactory) beanFactory;
}
@After
public void removeContext() {
contextFactory.clearContext();
attributes.set(null);
// Check that all temporary bean definitions are cleaned up
assertEquals(beanCount, beanFactory.getBeanDefinitionCount());
}
@Before
public void setUpContext() {
contextFactory.setContext(this);
beanCount = beanFactory.getBeanDefinitionCount();
}
@Test
public void testGetSimple() {
attributes.set(Collections.singletonMap("foo", "bar"));
assertEquals("bar", simple.getName());
}
@Test
public void testGetMultiple() throws Exception {
List<FutureTask<String>> tasks = new ArrayList<FutureTask<String>>();
for (int i = 0; i < 12; i++) {
final String value = "foo" + i;
FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
attributes.set(Collections.singletonMap("foo", value));
try {
return simple.getName();
}
finally {
attributes.set(null);
}
}
});
tasks.add(task);
taskExecutor.execute(task);
}
int i = 0;
for (FutureTask<String> task : tasks) {
assertEquals("foo" + i, task.get());
i++;
}
}
public static class SimpleContextFactory extends ContextFactorySupport {
private Object root;
@Override
public Object getContext() {
return root;
}
public void setContext(Object root) {
this.root = root;
}
public void clearContext() {
root = null;
}
}
public static interface Node {
String getName();
}
public static class Foo implements Node {
private String name;
private Log logger = LogFactory.getLog(getClass());
@Override
public String getName() {
return name;
}
public void setName(String name) {
logger.debug("Setting name: " + name);
this.name = name;
}
}
}

View File

@@ -1,27 +0,0 @@
package org.springframework.batch.core.scope.util;
public class ContextFactorySupport implements ContextFactory {
private int count = 0;
/**
* Returns this. Override for more sensible behaviour.
*
* @see org.springframework.batch.core.scope.util.ContextFactory#getContext()
*/
@Override
public Object getContext() {
return this;
}
/**
* Returns the context plus a counter, so each call is unique.
*
* @see org.springframework.batch.core.scope.util.ContextFactory#getContextId()
*/
@Override
public String getContextId() {
return getContext()+"#"+(count ++);
}
}

View File

@@ -1,228 +0,0 @@
/*
* Copyright 2006-2013 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<String, String> attributes;
public Map<String, String> 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<String> names = new ArrayList<String>();
private Map<String, String> map = new HashMap<String, String>();
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<String> getNames() {
return new ArrayList<String>(names);
}
public void setNames(List<String> names) {
this.names.addAll(names);
}
public Map<String, String> getMap() {
return new HashMap<String, String>(map);
}
public void setMap(Map<String, String> map) {
this.map.putAll(map);
}
}
public static class SimpleContextFactory extends ContextFactorySupport {
private Object root;
@Override
public Object getContext() {
return root;
}
public void setContext(Object root) {
this.root = root;
}
public void clearContext() {
root = null;
}
}
}

View File

@@ -1,63 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import java.text.SimpleDateFormat;
import java.util.Date;
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;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PlaceholderTargetSourceCustomEditorTests extends ContextFactorySupport {
@Autowired
@Qualifier("withEmbeddedDate")
private PlaceholderTargetSource withEmbeddedDate;
private Date date = new Date(1L);
@Override
public Object getContext() {
return this;
}
public Date getDate() {
return date;
}
@Test
public void testGetEmbeddedDate() {
Node target = (Node) withEmbeddedDate.getTarget();
String date = new SimpleDateFormat("yyyy/MM/dd").format(new Date(1L));
assertEquals("bar-"+date, target.getName());
}
public static interface Node {
String getName();
}
public static class Foo implements Node {
private String name;
public Foo() {
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -1,205 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class PlaceholderTargetSourceErrorTests extends ContextFactorySupport {
private Map<String, Object> map = Collections.singletonMap("foo.foo", (Object) "bar");
private Date date = new Date(1L);
@Override
public Object getContext() {
return this;
}
public String getFoo() {
return "bar";
}
public Map<String, Object> getMap() {
return map;
}
public Node getParent() {
return new Foo("spam");
}
public Long getLong() {
return 12345678912345L;
}
public Integer getInteger() {
return 4321;
}
public Date getDate() {
return date;
}
public String getGarbage() {
return null;
}
private PlaceholderTargetSource createValue(String name, String value) throws Exception {
String input = IOUtils.toString(new ClassPathResource(getClass().getSimpleName() + "-context.xml", getClass())
.getInputStream());
input = input.replace("<!-- INSERT -->", String.format("<property name=\"%s\" value=\"%s\" />", name, value));
Resource resource = new ByteArrayResource(input.getBytes());
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions(resource);
context.refresh();
// XmlBeanFactory context = new XmlBeanFactory(resource);
return (PlaceholderTargetSource) context.getBean("value");
}
@Test
public void testPartialReplaceSunnyDay() throws Exception {
Node target = (Node) createValue("name", "%{foo}-bar").getTarget();
assertEquals("bar-bar", target.getName());
}
@Test
public void testPartialReplaceMissingProperty() throws Exception {
try {
Node target = (Node) createValue("name", "%{garbage}-bar").getTarget();
assertEquals("%{garbage}-bar", target.getName());
fail("Expected IllegalStateException");
}
catch (IllegalStateException e) {
String message = e.getMessage();
assertTrue("Wrong message: " + message, message.toLowerCase().contains("cannot bind"));
}
}
@Test
public void testFullReplaceSunnyDay() throws Exception {
Node target = (Node) createValue("name", "%{foo}").getTarget();
assertEquals("bar", target.getName());
}
@Test
public void testFullReplaceMissingProperty() throws Exception {
try {
Node target = (Node) createValue("name", "%{garbage}").getTarget();
assertEquals("bar", target.getName());
fail("Expected IllegalStateException");
}
catch (BeanCreationException e) {
String message = e.getMessage();
assertTrue("Wrong message: " + message, message.toLowerCase().contains("cannot bind"));
}
}
@Test
public void testPartialReplaceIntegerToString() throws Exception {
Node target = (Node) createValue("name", "foo-%{integer}").getTarget();
assertEquals("foo-4321", target.getName());
}
@Test
public void testFullReplaceIntegerToString() throws Exception {
Node target = (Node) createValue("name", "%{integer}").getTarget();
assertEquals("4321", target.getName());
}
@Test
public void testFullReplaceIntegerToLong() throws Exception {
Node target = (Node) createValue("value", "%{integer}").getTarget();
assertEquals(4321L, target.getValue());
}
@Test
public void testFullReplaceIntegerToNode() throws Exception {
try {
Node target = (Node) createValue("parent", "%{integer}").getTarget();
assertEquals("4321", target.getParent());
fail("Expected IllegalArgumentException");
}
catch (Exception e) {
String message = e.getMessage();
assertTrue("Wrong message: " + message, message.toLowerCase().contains("cannot convert"));
}
}
public static interface Node {
String getName();
Date getDate();
Node getParent();
long getValue();
}
public static class Foo implements Node {
private String name;
private Date date;
private Node parent;
private long value;
public Foo() {
}
@Override
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public Foo(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public void setName(String name) {
this.name = name;
}
@Override
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
}

View File

@@ -1,298 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
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;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PlaceholderTargetSourceTests extends ContextFactorySupport {
@Autowired
@Qualifier("vanilla")
private PlaceholderTargetSource vanilla;
@Autowired
@Qualifier("simple")
private PlaceholderTargetSource simple;
@Autowired
@Qualifier("withLong")
private PlaceholderTargetSource withLong;
@Autowired
@Qualifier("withInteger")
private PlaceholderTargetSource withInteger;
@Autowired
@Qualifier("withList")
private PlaceholderTargetSource withList;
@Autowired
@Qualifier("withLiteralList")
private PlaceholderTargetSource withLiteralList;
@Autowired
@Qualifier("withMap")
private PlaceholderTargetSource withMap;
@Autowired
@Qualifier("withMultiple")
private PlaceholderTargetSource withMultiple;
@Autowired
@Qualifier("withMultipleStartAndEnd")
private PlaceholderTargetSource withMultipleStartAndEnd;
@Autowired
@Qualifier("withEmbeddedDate")
private PlaceholderTargetSource withEmbeddedDate;
@Autowired
@Qualifier("withDate")
private PlaceholderTargetSource withDate;
@Autowired
@Qualifier("compound")
private PlaceholderTargetSource compound;
@Autowired
@Qualifier("ref")
private PlaceholderTargetSource ref;
@Autowired
@Qualifier("value")
private PlaceholderTargetSource value;
private Map<String, Object> map = Collections.singletonMap("foo.foo", (Object) "bar");
private Date date = new Date(1L);
@Override
public Object getContext() {
return this;
}
public String getFoo() {
return "bar";
}
public Map<String, Object> getMap() {
return map;
}
public List<String> getList() {
return Arrays.asList("bar", "spam");
}
public Node getParent() {
return new Foo("spam");
}
public Long getLong() {
return 12345678912345L;
}
public Integer getInteger() {
return 4321;
}
public Date getDate() {
return date;
}
public String getGarbage() {
return null;
}
@Test
public void testAfterPropertiesSet() throws Exception {
PlaceholderTargetSource targetSource = new PlaceholderTargetSource();
try {
targetSource.afterPropertiesSet();
fail("Axpected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testGetVanilla() {
Node target = (Node) vanilla.getTarget();
assertEquals("foo", target.getName());
}
@Test
public void testGetSimple() {
Node target = (Node) simple.getTarget();
assertEquals("bar", target.getName());
}
@Test
public void testGetCompound() {
Node target = (Node) compound.getTarget();
assertEquals("bar-bar", target.getName());
}
@Test
public void testGetRef() {
Node target = (Node) ref.getTarget();
assertEquals("foo", target.getParent().getName());
}
@Test
public void testGetValue() {
Node target = (Node) value.getTarget();
assertEquals("spam", target.getParent().getName());
}
@Test
public void testGetLong() {
Node target = (Node) withLong.getTarget();
assertEquals("bar-12345678912345", target.getName());
}
@Test
public void testGetInteger() {
Node target = (Node) withInteger.getTarget();
assertEquals("bar-4321", target.getName());
}
@Test
public void testGetList() {
Node target = (Node) withList.getTarget();
assertEquals(3, target.getList().size());
assertEquals("[bar, foo-4321, bar-4321]", target.getList().toString());
}
@Test
public void testGetLiteralList() {
Node target = (Node) withLiteralList.getTarget();
assertEquals(2, target.getList().size());
assertEquals("[bar, spam]", target.getList().toString());
}
@Test
public void testGetMap() {
Node target = (Node) withMap.getTarget();
assertEquals(3, target.getMap().size());
assertEquals("{foo=bar, bar=foo-4321, spam=[bar, spam]}", target.getMap().toString());
}
@Test
public void testGetMultiple() {
Node target = (Node) withMultiple.getTarget();
assertEquals("bar-4321-4321", target.getName());
}
@Test
public void testGetMultipleStartAndEnd() {
Node target = (Node) withMultipleStartAndEnd.getTarget();
assertEquals("4321-4321", target.getName());
}
@Test
public void testGetEmbeddedDate() {
Node target = (Node) withEmbeddedDate.getTarget();
String date = new Date(1L).toString();
assertEquals("bar-"+date, target.getName());
}
@Test
public void testGetDate() {
Node target = (Node) withDate.getTarget();
assertEquals(new Date(1L), target.getDate());
}
public static interface Node {
String getName();
Date getDate();
Node getParent();
List<String> getList();
Map<String, Object> getMap();
}
public static class Foo implements Node {
private String name;
private Date date;
private Node parent;
private List<String> list;
private Map<String, Object> map;
public Foo() {
}
public Foo(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public void setName(String name) {
this.name = name;
}
@Override
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
public List<String> getList() {
return list;
}
@Override
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
}

View File

@@ -1,72 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
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;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class SimplePlaceholderTargetSourceTests {
@Autowired
@Qualifier("simple")
private Node simple;
@Autowired
private SimpleContextFactory contextFactory;
@Test
public void testGetSimple() {
contextFactory.set("bar");
assertEquals("bar", simple.getName());
contextFactory.clear();
}
public static class SimpleContextFactory extends ContextFactorySupport {
private ThreadLocal<String> fooHolder = new ThreadLocal<String>();
@Override
public Object getContext() {
return this;
}
public void set(String value) {
fooHolder.set(value);
}
public void clear() {
fooHolder.set(null);
}
public String getFoo() {
return fooHolder.get();
}
}
public static interface Node {
String getName();
}
public static class Foo implements Node {
private String name;
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -1,39 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.After;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
public class StepContextFactoryTests {
private StepContextFactory factory = new StepContextFactory();
@After
public void cleanUp() {
StepSynchronizationManager.close();
StepSynchronizationManager.close();
}
@Test
public void testGetContext() {
StepExecution stepExecution = new StepExecution("foo", new JobExecution(11L));
StepContext context = StepSynchronizationManager.register(stepExecution);
assertEquals(context, factory.getContext());
}
@Test
public void testGetContextId() {
StepSynchronizationManager.register(new StepExecution("foo", new JobExecution(11L), 0L));
Object id1 = factory.getContextId();
StepSynchronizationManager.register(new StepExecution("foo", new JobExecution(12L), 1L));
Object id2 = factory.getContextId();
assertFalse(id2.equals(id1));
}
}