diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml
index ee9cfdc3f..d7c90ce02 100644
--- a/spring-batch-core/pom.xml
+++ b/spring-batch-core/pom.xml
@@ -130,6 +130,12 @@
mockito-alltest
+
+ javax.inject
+ javax.inject
+ 1
+ test
+
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java
index 94f2085a5..e1e016a60 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java
@@ -21,7 +21,6 @@ import javax.batch.runtime.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.util.Assert;
/**
@@ -30,23 +29,23 @@ import org.springframework.util.Assert;
* obtain the related contextual information.
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class JobContext implements javax.batch.runtime.context.JobContext {
- private JobExecution jobExecution;
private Object transientUserData;
- private JobParametersConverter jobParametersConverter;
+ private Properties properties;
+ private JobExecution jobExecution;
- /**
+ /**
* @param jobExecution for the related job
*/
- public JobContext(JobExecution jobExecution, JobParametersConverter jobParametersConverter) {
+ public JobContext(JobExecution jobExecution, Properties properties) {
Assert.notNull(jobExecution, "A JobExecution is required");
- Assert.notNull(jobParametersConverter, "A ParametersConverter is required");
- this.jobExecution = jobExecution;
- this.jobParametersConverter = jobParametersConverter;
+ this.properties = properties;
+ this.jobExecution = jobExecution;
}
/* (non-Javadoc)
@@ -94,7 +93,7 @@ public class JobContext implements javax.batch.runtime.context.JobContext {
*/
@Override
public Properties getProperties() {
- return jobParametersConverter.getProperties(this.jobExecution.getJobParameters());
+ return properties != null ? properties : new Properties();
}
/* (non-Javadoc)
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java
index f57a8ce22..f4f262d68 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContext.java
@@ -24,7 +24,6 @@ import javax.batch.runtime.Metric;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.util.Assert;
/**
@@ -33,20 +32,20 @@ import org.springframework.util.Assert;
* obtain the related contextual information.
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class StepContext implements javax.batch.runtime.context.StepContext {
private StepExecution stepExecution;
private Object transientUserData;
- private JobParametersConverter jobParametersConveter;
+ private Properties properties = new Properties();
- public StepContext(StepExecution stepExecution, JobParametersConverter jobParametersConveter) {
+ public StepContext(StepExecution stepExecution, Properties properties) {
Assert.notNull(stepExecution, "A StepExecution is required");
- Assert.notNull(jobParametersConveter, "A ParametersConverter is required");
this.stepExecution = stepExecution;
- this.jobParametersConveter = jobParametersConveter;
+ this.properties = properties;
}
/* (non-Javadoc)
@@ -86,8 +85,7 @@ public class StepContext implements javax.batch.runtime.context.StepContext {
*/
@Override
public Properties getProperties() {
- //TODO: Fix this...this should be properties, not parameters. Waiting on BATCH-2001
- return jobParametersConveter.getProperties(this.stepExecution.getJobParameters());
+ return properties != null ? properties : new Properties();
}
/* (non-Javadoc)
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java
index da05c3ecb..20ca37308 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java
@@ -15,49 +15,39 @@
*/
package org.springframework.batch.core.jsr;
-import javax.sql.DataSource;
+import java.util.Properties;
-import org.springframework.batch.core.converter.JobParametersConverter;
+import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.beans.factory.FactoryBean;
-import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.util.Assert;
/**
* {@link FactoryBean} implementation used to create {@link javax.batch.runtime.context.StepContext}
* instances within the step scope.
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
-public class StepContextFactoryBean implements FactoryBean, InitializingBean {
-
+public class StepContextFactoryBean implements FactoryBean {
@Autowired
- public DataSource dataSource;
- private JobParametersConverter jobParametersConveter;
+ private BatchPropertyContext batchPropertyContext;
- /* (non-Javadoc)
- * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
- */
- @Override
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(dataSource, "A DataSource is required");
-
- jobParametersConveter = new JsrJobParametersConverter(dataSource);
- }
-
- /* (non-Javadoc)
- * @see org.springframework.beans.factory.FactoryBean#getObject()
- */
+ /* (non-Javadoc)
+ * @see org.springframework.beans.factory.FactoryBean#getObject()
+ */
@Override
public StepContext getObject() throws Exception {
- return new StepContext(StepSynchronizationManager.getContext().getStepExecution(), jobParametersConveter);
+ org.springframework.batch.core.StepExecution stepExecution = StepSynchronizationManager.getContext().getStepExecution();
+ Properties properties = batchPropertyContext.getBatchProperties(stepExecution.getStepName());
+
+ return new StepContext(stepExecution, properties);
}
- /* (non-Javadoc)
- * @see org.springframework.beans.factory.FactoryBean#getObjectType()
- */
+ /* (non-Javadoc)
+ * @see org.springframework.beans.factory.FactoryBean#getObjectType()
+ */
@Override
public Class> getObjectType() {
return StepContext.class;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java
new file mode 100644
index 000000000..2f0bd383e
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 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.jsr.configuration.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import javax.batch.api.BatchProperty;
+import javax.batch.api.Batchlet;
+import javax.batch.api.chunk.ItemProcessor;
+import javax.batch.api.chunk.ItemReader;
+import javax.batch.api.chunk.ItemWriter;
+import javax.batch.api.chunk.listener.ItemProcessListener;
+import javax.batch.api.chunk.listener.ItemReadListener;
+import javax.batch.api.chunk.listener.ItemWriteListener;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.flow.JobExecutionDecider;
+import org.springframework.batch.repeat.CompletionPolicy;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ *
+ * {@link BeanPostProcessor} implementation used to inject JSR-352 String properties into batch artifact fields
+ * that are marked with the {@link BatchProperty} annotation.
+ *
+ *
+ * @author Chris Schaefer
+ * @since 3.0
+ */
+public class BatchPropertyBeanPostProcessor implements BeanPostProcessor {
+ @Autowired
+ private BatchPropertyContext batchPropertyContext;
+ private Log logger = LogFactory.getLog(getClass());
+ private Set> requiredAnnotations = new HashSet>();
+
+ public BatchPropertyBeanPostProcessor() {
+ setRequiredAnnotations();
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
+ if (!isBatchArtifact(bean)) {
+ return bean;
+ }
+
+ final Properties artifactProperties = batchPropertyContext.getBatchProperties(beanName);
+
+ if (artifactProperties.isEmpty()) {
+ return bean;
+ }
+
+ injectBatchProperties(bean, artifactProperties);
+
+ return bean;
+ }
+
+ private void setRequiredAnnotations() {
+ ClassLoader cl = BatchPropertyBeanPostProcessor.class.getClassLoader();
+
+ try {
+ this.requiredAnnotations.add((Class extends Annotation>) cl.loadClass("javax.inject.Inject"));
+ } catch (ClassNotFoundException ex) {
+ logger.warn("javax.inject.Inject not found - @BatchProperty marked fields will not be processed.");
+ }
+
+ this.requiredAnnotations.add(BatchProperty.class);
+ }
+
+ private boolean isBatchArtifact(Object bean) {
+ return (bean instanceof ItemReader) ||
+ (bean instanceof ItemProcessor) ||
+ (bean instanceof ItemWriter) ||
+ (bean instanceof CompletionPolicy) ||
+ (bean instanceof Batchlet) ||
+ (bean instanceof ItemReadListener) ||
+ (bean instanceof ItemProcessListener) ||
+ (bean instanceof ItemWriteListener) ||
+ (bean instanceof JobExecutionDecider) ||
+ (bean instanceof Step) ||
+ (bean instanceof Job);
+ }
+
+ private void injectBatchProperties(final Object bean, final Properties artifactProperties) {
+ ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ if (isValidFieldModifier(field) && isAnnotated(field)) {
+ boolean isAccessible = field.isAccessible();
+ field.setAccessible(true);
+
+ String batchProperty = getBatchPropertyFieldValue(field, artifactProperties);
+
+ if (batchProperty != null) {
+ field.set(bean, batchProperty);
+ }
+
+ field.setAccessible(isAccessible);
+ }
+ }
+ });
+ }
+
+ private String getBatchPropertyFieldValue(Field field, Properties batchArtifactProperties) {
+ BatchProperty batchProperty = field.getAnnotation(BatchProperty.class);
+
+ if (!"".equals(batchProperty.name())) {
+ return getBatchProperty(batchProperty.name(), batchArtifactProperties);
+ }
+
+ return getBatchProperty(field.getName(), batchArtifactProperties);
+ }
+
+ private String getBatchProperty(String propertyKey, Properties batchArtifactProperties) {
+ if (batchArtifactProperties.containsKey(propertyKey)) {
+ return (String) batchArtifactProperties.get(propertyKey);
+ }
+
+ return null;
+ }
+
+ private boolean isAnnotated(Field field) {
+ for (Class extends Annotation> annotation : requiredAnnotations) {
+ if(!field.isAnnotationPresent(annotation)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isValidFieldModifier(Field field) {
+ return !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers());
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java
new file mode 100644
index 000000000..4bcc4c12d
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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.jsr.configuration.support;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * Simple context object to hold parsed JSR-352 batch properties, mapping properties
+ * to beans / "batch artifacts". Used internally when parsing property tags from a batch
+ * configuration file and to obtain corresponding values when injecting into batch artifacts.
+ *
+ *
+ * @author Chris Schaefer
+ * @since 3.0
+ */
+public class BatchPropertyContext {
+ private ConcurrentHashMap batchProperties = new ConcurrentHashMap();
+
+ /**
+ *
+ * Adds each of the provided {@link BatchPropertyContext} objects to the existing propery
+ * context.
+ *
+ * Obtains the batch {@link Properties} for the provided bean name / batch artifact. The returned
+ * {@link Properties} will also contain any job level properties that have been set.
+ *
+ *
+ * @param beanName the bean name representing the batch artifact to obtain properties for
+ * @return the {@link Properties} for the provided batch artifact
+ */
+ public Properties getBatchProperties(String beanName) {
+ Properties properties = new Properties();
+
+ if (batchProperties.containsKey(beanName)) {
+ properties.putAll(batchProperties.get(beanName));
+ }
+
+ for (String jobLevelProperty : batchProperties.keySet()) {
+ if (jobLevelProperty.startsWith("job-")) {
+ if (batchProperties.containsKey(jobLevelProperty)) {
+ properties.putAll(batchProperties.get(jobLevelProperty));
+ }
+
+ break;
+ }
+ }
+
+ return properties;
+ }
+
+ /**
+ *
+ * Simple object to encapsulate batch properties of a given bean / batch artifact.
+ *
+ * Creates a new entry instance using the provided bean name representing batch artifact
+ * and its associated {@link Properties}.
+ *
+ *
+ * @param beanName the bean name representing the batch artifact
+ * @param properties the associated {@link Properties}
+ */
+ public BatchPropertyContextEntry(String beanName, Properties properties) {
+ this.beanName = beanName;
+ this.properties = properties;
+ }
+
+ /**
+ *
+ * Obtains the bean name of the batch artifact this entry is associated with.
+ *
+ *
+ * @return the bean name of the batch artifact
+ */
+ public String getBeanName() {
+ return beanName;
+ }
+
+ /**
+ *
+ * Obtains the batch {@link Properties} that are associated with this entry.
+ *
+ *
+ * @return the batch {@link Properties}
+ */
+ public Properties getProperties() {
+ return properties;
+ }
+ }
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrAutowiredAnnotationBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrAutowiredAnnotationBeanPostProcessor.java
new file mode 100644
index 000000000..27d6ba06e
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrAutowiredAnnotationBeanPostProcessor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.jsr.configuration.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import javax.batch.api.BatchProperty;
+import org.springframework.beans.factory.annotation.InjectionMetadata;
+
+/**
+ *
This class overrides methods in the copied {@link SpringAutowiredAnnotationBeanPostProcessor} class
+ * to check for the {@link @BatchProperty} annotation before processing injection annotations. If the annotation
+ * is found, further injection processing for the field is skipped.
+ */
+public class JsrAutowiredAnnotationBeanPostProcessor extends SpringAutowiredAnnotationBeanPostProcessor {
+ @Override
+ protected InjectionMetadata findAutowiringMetadata(Class> clazz) {
+ return super.buildAutowiringMetadata(clazz);
+ }
+
+ @Override
+ protected Annotation findAutowiredAnnotation(AccessibleObject ao) {
+ if (ao.getAnnotation(BatchProperty.class) != null) {
+ return null;
+ }
+
+ return super.findAutowiredAnnotation(ao);
+ }
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/SpringAutowiredAnnotationBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/SpringAutowiredAnnotationBeanPostProcessor.java
new file mode 100644
index 000000000..33eacccc6
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/SpringAutowiredAnnotationBeanPostProcessor.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright 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.jsr.configuration.support;
+
+import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.TypeConverter;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.InjectionMetadata;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ *
This is a copy of AutowiredAnnotationBeanPostProcessor with modifications allow a subclass to
+ * do additional checks on other field annotations before processing injection annotations.
+ *
+ *
This class is considered a quick work around and needs to be refactored / removed.
+ *
+ *
The in addition to making this class package private, the following methods were modified to be protected:
+ *
findAutowiringMetadata(Class> clazz)
+ *
buildAutowiringMetadata(Class> clazz)
+ *
findAutowiredAnnotation(AccessibleObject ao)
+ */
+class SpringAutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
+ implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final Set> autowiredAnnotationTypes =
+ new LinkedHashSet>();
+
+ private String requiredParameterName = "required";
+
+ private boolean requiredParameterValue = true;
+
+ private int order = Ordered.LOWEST_PRECEDENCE - 2;
+
+ private ConfigurableListableBeanFactory beanFactory;
+
+ private final Map, Constructor>[]> candidateConstructorsCache =
+ new ConcurrentHashMap, Constructor>[]>(64);
+
+ private final Map, InjectionMetadata> injectionMetadataCache =
+ new ConcurrentHashMap, InjectionMetadata>(64);
+
+
+ /**
+ * Create a new AutowiredAnnotationBeanPostProcessor
+ * for Spring's standard {@link org.springframework.beans.factory.annotation.Autowired} annotation.
+ *
Also supports JSR-330's {@link javax.inject.Inject} annotation, if available.
+ */
+ @SuppressWarnings("unchecked")
+ public SpringAutowiredAnnotationBeanPostProcessor() {
+ this.autowiredAnnotationTypes.add(Autowired.class);
+ this.autowiredAnnotationTypes.add(Value.class);
+ ClassLoader cl = SpringAutowiredAnnotationBeanPostProcessor.class.getClassLoader();
+ try {
+ this.autowiredAnnotationTypes.add((Class extends Annotation>) cl.loadClass("javax.inject.Inject"));
+ logger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
+ }
+ catch (ClassNotFoundException ex) {
+ // JSR-330 API not available - simply skip.
+ }
+ }
+
+
+ /**
+ * Set the 'autowired' annotation type, to be used on constructors, fields,
+ * setter methods and arbitrary config methods.
+ *
The default autowired annotation type is the Spring-provided
+ * {@link Autowired} annotation, as well as {@link Value}.
+ *
This setter property exists so that developers can provide their own
+ * (non-Spring-specific) annotation type to indicate that a member is
+ * supposed to be autowired.
+ */
+ public void setAutowiredAnnotationType(Class extends Annotation> autowiredAnnotationType) {
+ Assert.notNull(autowiredAnnotationType, "'autowiredAnnotationType' must not be null");
+ this.autowiredAnnotationTypes.clear();
+ this.autowiredAnnotationTypes.add(autowiredAnnotationType);
+ }
+
+ /**
+ * Set the 'autowired' annotation types, to be used on constructors, fields,
+ * setter methods and arbitrary config methods.
+ *
The default autowired annotation type is the Spring-provided
+ * {@link Autowired} annotation, as well as {@link Value}.
+ *
This setter property exists so that developers can provide their own
+ * (non-Spring-specific) annotation types to indicate that a member is
+ * supposed to be autowired.
+ */
+ public void setAutowiredAnnotationTypes(Set> autowiredAnnotationTypes) {
+ Assert.notEmpty(autowiredAnnotationTypes, "'autowiredAnnotationTypes' must not be empty");
+ this.autowiredAnnotationTypes.clear();
+ this.autowiredAnnotationTypes.addAll(autowiredAnnotationTypes);
+ }
+
+ /**
+ * Set the name of a parameter of the annotation that specifies
+ * whether it is required.
+ * @see #setRequiredParameterValue(boolean)
+ */
+ public void setRequiredParameterName(String requiredParameterName) {
+ this.requiredParameterName = requiredParameterName;
+ }
+
+ /**
+ * Set the boolean value that marks a dependency as required
+ *
For example if using 'required=true' (the default),
+ * this value should be true; but if using
+ * 'optional=false', this value should be false.
+ * @see #setRequiredParameterName(String)
+ */
+ public void setRequiredParameterValue(boolean requiredParameterValue) {
+ this.requiredParameterValue = requiredParameterValue;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public int getOrder() {
+ return this.order;
+ }
+
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
+ throw new IllegalArgumentException(
+ "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory");
+ }
+ this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
+ }
+
+
+ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class> beanType, String beanName) {
+ if (beanType != null) {
+ InjectionMetadata metadata = findAutowiringMetadata(beanType);
+ metadata.checkConfigMembers(beanDefinition);
+ }
+ }
+
+ @Override
+ public Constructor>[] determineCandidateConstructors(Class> beanClass, String beanName) throws BeansException {
+ // Quick check on the concurrent map first, with minimal locking.
+ Constructor>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
+ if (candidateConstructors == null) {
+ synchronized (this.candidateConstructorsCache) {
+ candidateConstructors = this.candidateConstructorsCache.get(beanClass);
+ if (candidateConstructors == null) {
+ Constructor>[] rawCandidates = beanClass.getDeclaredConstructors();
+ List> candidates = new ArrayList>(rawCandidates.length);
+ Constructor> requiredConstructor = null;
+ Constructor> defaultConstructor = null;
+ for (Constructor> candidate : rawCandidates) {
+ Annotation annotation = findAutowiredAnnotation(candidate);
+ if (annotation != null) {
+ if (requiredConstructor != null) {
+ throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
+ ". Found another constructor with 'required' Autowired annotation: " +
+ requiredConstructor);
+ }
+ if (candidate.getParameterTypes().length == 0) {
+ throw new IllegalStateException(
+ "Autowired annotation requires at least one argument: " + candidate);
+ }
+ boolean required = determineRequiredStatus(annotation);
+ if (required) {
+ if (!candidates.isEmpty()) {
+ throw new BeanCreationException(
+ "Invalid autowire-marked constructors: " + candidates +
+ ". Found another constructor with 'required' Autowired annotation: " +
+ requiredConstructor);
+ }
+ requiredConstructor = candidate;
+ }
+ candidates.add(candidate);
+ }
+ else if (candidate.getParameterTypes().length == 0) {
+ defaultConstructor = candidate;
+ }
+ }
+ if (!candidates.isEmpty()) {
+ // Add default constructor to list of optional constructors, as fallback.
+ if (requiredConstructor == null && defaultConstructor != null) {
+ candidates.add(defaultConstructor);
+ }
+ candidateConstructors = candidates.toArray(new Constructor[candidates.size()]);
+ }
+ else {
+ candidateConstructors = new Constructor[0];
+ }
+ this.candidateConstructorsCache.put(beanClass, candidateConstructors);
+ }
+ }
+ }
+ return (candidateConstructors.length > 0 ? candidateConstructors : null);
+ }
+
+ @Override
+ public PropertyValues postProcessPropertyValues(
+ PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
+
+ InjectionMetadata metadata = findAutowiringMetadata(bean.getClass());
+ try {
+ metadata.inject(bean, beanName, pvs);
+ }
+ catch (Throwable ex) {
+ throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
+ }
+ return pvs;
+ }
+
+ /**
+ * 'Native' processing method for direct calls with an arbitrary target instance,
+ * resolving all of its fields and methods which are annotated with @Autowired.
+ * @param bean the target instance to process
+ * @throws BeansException if autowiring failed
+ */
+ public void processInjection(Object bean) throws BeansException {
+ Class> clazz = bean.getClass();
+ InjectionMetadata metadata = findAutowiringMetadata(clazz);
+ try {
+ metadata.inject(bean, null, null);
+ }
+ catch (Throwable ex) {
+ throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
+ }
+ }
+
+
+ protected InjectionMetadata findAutowiringMetadata(Class> clazz) {
+ // Quick check on the concurrent map first, with minimal locking.
+ InjectionMetadata metadata = this.injectionMetadataCache.get(clazz);
+ if (metadata == null) {
+ synchronized (this.injectionMetadataCache) {
+ metadata = this.injectionMetadataCache.get(clazz);
+ if (metadata == null) {
+ metadata = buildAutowiringMetadata(clazz);
+ this.injectionMetadataCache.put(clazz, metadata);
+ }
+ }
+ }
+ return metadata;
+ }
+
+ protected InjectionMetadata buildAutowiringMetadata(Class> clazz) {
+ LinkedList elements = new LinkedList();
+ Class> targetClass = clazz;
+
+ do {
+ LinkedList currElements = new LinkedList();
+ for (Field field : targetClass.getDeclaredFields()) {
+ Annotation annotation = findAutowiredAnnotation(field);
+ if (annotation != null) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Autowired annotation is not supported on static fields: " + field);
+ }
+ continue;
+ }
+ boolean required = determineRequiredStatus(annotation);
+ currElements.add(new AutowiredFieldElement(field, required));
+ }
+ }
+ for (Method method : targetClass.getDeclaredMethods()) {
+ Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
+ Annotation annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
+ findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method);
+ if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Autowired annotation is not supported on static methods: " + method);
+ }
+ continue;
+ }
+ if (method.getParameterTypes().length == 0) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Autowired annotation should be used on methods with actual parameters: " + method);
+ }
+ }
+ boolean required = determineRequiredStatus(annotation);
+ PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method);
+ currElements.add(new AutowiredMethodElement(method, required, pd));
+ }
+ }
+ elements.addAll(0, currElements);
+ targetClass = targetClass.getSuperclass();
+ }
+ while (targetClass != null && targetClass != Object.class);
+
+ return new InjectionMetadata(clazz, elements);
+ }
+
+ protected Annotation findAutowiredAnnotation(AccessibleObject ao) {
+ for (Class extends Annotation> type : this.autowiredAnnotationTypes) {
+ Annotation annotation = AnnotationUtils.getAnnotation(ao, type);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Obtain all beans of the given type as autowire candidates.
+ * @param type the type of the bean
+ * @return the target beans, or an empty Collection if no bean of this type is found
+ * @throws BeansException if bean retrieval failed
+ */
+ protected Map findAutowireCandidates(Class type) throws BeansException {
+ if (this.beanFactory == null) {
+ throw new IllegalStateException("No BeanFactory configured - " +
+ "override the getBeanOfType method or specify the 'beanFactory' property");
+ }
+ return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
+ }
+
+ /**
+ * Determine if the annotated field or method requires its dependency.
+ *
A 'required' dependency means that autowiring should fail when no beans
+ * are found. Otherwise, the autowiring process will simply bypass the field
+ * or method when no beans are found.
+ * @param annotation the Autowired annotation
+ * @return whether the annotation indicates that a dependency is required
+ */
+ protected boolean determineRequiredStatus(Annotation annotation) {
+ try {
+ Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName);
+ if (method == null) {
+ // annotations like @Inject and @Value don't have a method (attribute) named "required"
+ // -> default to required status
+ return true;
+ }
+ return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation));
+ }
+ catch (Exception ex) {
+ // an exception was thrown during reflective invocation of the required attribute
+ // -> default to required status
+ return true;
+ }
+ }
+
+ /**
+ * Register the specified bean as dependent on the autowired beans.
+ */
+ private void registerDependentBeans(String beanName, Set autowiredBeanNames) {
+ if (beanName != null) {
+ for (String autowiredBeanName : autowiredBeanNames) {
+ if (this.beanFactory.containsBean(autowiredBeanName)) {
+ this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Autowiring by type from bean name '" + beanName +
+ "' to bean named '" + autowiredBeanName + "'");
+ }
+ }
+ }
+ }
+
+ /**
+ * Resolve the specified cached method argument or field value.
+ */
+ private Object resolvedCachedArgument(String beanName, Object cachedArgument) {
+ if (cachedArgument instanceof DependencyDescriptor) {
+ DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument;
+ TypeConverter typeConverter = this.beanFactory.getTypeConverter();
+ return this.beanFactory.resolveDependency(descriptor, beanName, null, typeConverter);
+ }
+ else if (cachedArgument instanceof RuntimeBeanReference) {
+ return this.beanFactory.getBean(((RuntimeBeanReference) cachedArgument).getBeanName());
+ }
+ else {
+ return cachedArgument;
+ }
+ }
+
+
+ /**
+ * Class representing injection information about an annotated field.
+ */
+ private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
+
+ private final boolean required;
+
+ private volatile boolean cached = false;
+
+ private volatile Object cachedFieldValue;
+
+ public AutowiredFieldElement(Field field, boolean required) {
+ super(field, null);
+ this.required = required;
+ }
+
+ @Override
+ protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
+ Field field = (Field) this.member;
+ try {
+ Object value;
+ if (this.cached) {
+ value = resolvedCachedArgument(beanName, this.cachedFieldValue);
+ }
+ else {
+ DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
+ Set autowiredBeanNames = new LinkedHashSet(1);
+ TypeConverter typeConverter = beanFactory.getTypeConverter();
+ value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
+ synchronized (this) {
+ if (!this.cached) {
+ if (value != null || this.required) {
+ this.cachedFieldValue = descriptor;
+ registerDependentBeans(beanName, autowiredBeanNames);
+ if (autowiredBeanNames.size() == 1) {
+ String autowiredBeanName = autowiredBeanNames.iterator().next();
+ if (beanFactory.containsBean(autowiredBeanName)) {
+ if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
+ this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName);
+ }
+ }
+ }
+ }
+ else {
+ this.cachedFieldValue = null;
+ }
+ this.cached = true;
+ }
+ }
+ }
+ if (value != null) {
+ ReflectionUtils.makeAccessible(field);
+ field.set(bean, value);
+ }
+ }
+ catch (Throwable ex) {
+ throw new BeanCreationException("Could not autowire field: " + field, ex);
+ }
+ }
+ }
+
+
+ /**
+ * Class representing injection information about an annotated method.
+ */
+ private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {
+
+ private final boolean required;
+
+ private volatile boolean cached = false;
+
+ private volatile Object[] cachedMethodArguments;
+
+ public AutowiredMethodElement(Method method, boolean required, PropertyDescriptor pd) {
+ super(method, pd);
+ this.required = required;
+ }
+
+ @Override
+ protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
+ if (checkPropertySkipping(pvs)) {
+ return;
+ }
+ Method method = (Method) this.member;
+ try {
+ Object[] arguments;
+ if (this.cached) {
+ // Shortcut for avoiding synchronization...
+ arguments = resolveCachedArguments(beanName);
+ }
+ else {
+ Class>[] paramTypes = method.getParameterTypes();
+ arguments = new Object[paramTypes.length];
+ DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
+ Set autowiredBeanNames = new LinkedHashSet(paramTypes.length);
+ TypeConverter typeConverter = beanFactory.getTypeConverter();
+ for (int i = 0; i < arguments.length; i++) {
+ MethodParameter methodParam = new MethodParameter(method, i);
+ GenericTypeResolver.resolveParameterType(methodParam, bean.getClass());
+ descriptors[i] = new DependencyDescriptor(methodParam, this.required);
+ arguments[i] = beanFactory.resolveDependency(
+ descriptors[i], beanName, autowiredBeanNames, typeConverter);
+ if (arguments[i] == null && !this.required) {
+ arguments = null;
+ break;
+ }
+ }
+ synchronized (this) {
+ if (!this.cached) {
+ if (arguments != null) {
+ this.cachedMethodArguments = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ this.cachedMethodArguments[i] = descriptors[i];
+ }
+ registerDependentBeans(beanName, autowiredBeanNames);
+ if (autowiredBeanNames.size() == paramTypes.length) {
+ Iterator it = autowiredBeanNames.iterator();
+ for (int i = 0; i < paramTypes.length; i++) {
+ String autowiredBeanName = it.next();
+ if (beanFactory.containsBean(autowiredBeanName)) {
+ if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
+ this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName);
+ }
+ }
+ }
+ }
+ }
+ else {
+ this.cachedMethodArguments = null;
+ }
+ this.cached = true;
+ }
+ }
+ }
+ if (arguments != null) {
+ ReflectionUtils.makeAccessible(method);
+ method.invoke(bean, arguments);
+ }
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ catch (Throwable ex) {
+ throw new BeanCreationException("Could not autowire method: " + method, ex);
+ }
+ }
+
+ private Object[] resolveCachedArguments(String beanName) {
+ if (this.cachedMethodArguments == null) {
+ return null;
+ }
+ Object[] arguments = new Object[this.cachedMethodArguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ arguments[i] = resolvedCachedArgument(beanName, this.cachedMethodArguments[i]);
+ }
+ return arguments;
+ }
+ }
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java
index a906a897f..ff32cb0dd 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java
@@ -30,25 +30,25 @@ import org.w3c.dom.Element;
* attribute is expected to point to an implementation of Tasklet).
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class BatchletParser extends AbstractSingleBeanDefinitionParser {
-
private static final String REF = "ref";
- public void parseBatchlet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd,
- ParserContext parserContext) {
-
+ public void parseBatchlet(Element batchletElement, AbstractBeanDefinition bd, ParserContext parserContext) {
bd.setBeanClass(StepFactoryBean.class);
bd.setAttribute("isNamespaceStep", false);
- String taskletRef = taskletElement.getAttribute(REF);
+ String taskletRef = batchletElement.getAttribute(REF);
if (StringUtils.hasText(taskletRef)) {
bd.getPropertyValues().addPropertyValue("tasklet", new RuntimeBeanReference(taskletRef));
}
bd.setRole(BeanDefinition.ROLE_SUPPORT);
- bd.setSource(parserContext.extractSource(taskletElement));
+ bd.setSource(parserContext.extractSource(batchletElement));
+
+ new PropertyParser(taskletRef, parserContext).parseProperties(batchletElement);
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java
index 949fd557d..b3dd2c3f6 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java
@@ -41,11 +41,11 @@ import org.w3c.dom.NodeList;
* {@link ItemProcessor}, and {@link ItemWriter}).
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*
*/
public class ChunkParser {
-
private static final String TIME_LIMIT_ATTRIBUTE = "time-limit";
private static final String ITEM_COUNT_ATTRIBUTE = "item-count";
private static final String CHECKPOINT_ALGORITHM_ELEMENT = "checkpoint-algorithm";
@@ -77,8 +77,7 @@ public class ChunkParser {
parseSimpleAttribute(element, propertyValues, ITEM_COUNT_ATTRIBUTE, "commitInterval");
parseSimpleAttribute(element, propertyValues, TIME_LIMIT_ATTRIBUTE, "timeout");
} else if(checkpointPolicy.equals(CUSTOM_CHECKPOINT_POLICY)) {
- parseCustomCheckpointAlgorithm(element, parserContext,
- propertyValues);
+ parseCustomCheckpointAlgorithm(element, parserContext, propertyValues);
}
}
@@ -102,26 +101,31 @@ public class ChunkParser {
}
@SuppressWarnings({"unchecked", "rawtypes"})
- private void parseChildElement(Element element,
- ParserContext parserContext, MutablePropertyValues propertyValues,
- Node nd) {
+ private void parseChildElement(Element element, ParserContext parserContext,
+ MutablePropertyValues propertyValues, Node nd) {
if (nd instanceof Element) {
Element nestedElement = (Element) nd;
String name = nestedElement.getLocalName();
-
String artifactName = nestedElement.getAttribute(REF_ATTRIBUTE);
+
if(name.equals(READER_ELEMENT)) {
if (StringUtils.hasText(artifactName)) {
propertyValues.addPropertyValue("itemReader", new RuntimeBeanReference(artifactName));
}
+
+ new PropertyParser(artifactName, parserContext).parseProperties(nestedElement);
} else if(name.equals(PROCESSOR_ELEMENT)) {
if (StringUtils.hasText(artifactName)) {
propertyValues.addPropertyValue("itemProcessor", new RuntimeBeanReference(artifactName));
}
+
+ new PropertyParser(artifactName, parserContext).parseProperties(nestedElement);
} else if(name.equals(WRITER_ELEMENT)) {
if (StringUtils.hasText(artifactName)) {
propertyValues.addPropertyValue("itemWriter", new RuntimeBeanReference(artifactName));
}
+
+ new PropertyParser(artifactName, parserContext).parseProperties(nestedElement);
} else if(name.equals(SKIPPABLE_EXCEPTION_CLASSES_ELEMENT)) {
ManagedMap exceptionClasses = new ExceptionElementParser().parse(element, parserContext, SKIPPABLE_EXCEPTION_CLASSES_ELEMENT);
if(exceptionClasses != null) {
@@ -146,8 +150,7 @@ public class ChunkParser {
}
}
- private void parseCustomCheckpointAlgorithm(Element element,
- ParserContext parserContext, MutablePropertyValues propertyValues) {
+ private void parseCustomCheckpointAlgorithm(Element element, ParserContext parserContext, MutablePropertyValues propertyValues) {
List elements = DomUtils.getChildElementsByTagName(element, CHECKPOINT_ALGORITHM_ELEMENT);
if(elements.size() == 1) {
@@ -157,6 +160,8 @@ public class ChunkParser {
if(StringUtils.hasText(name)) {
propertyValues.addPropertyValue("chunkCompletionPolicy", new RuntimeBeanReference(name));
}
+
+ new PropertyParser(name, parserContext).parseProperties(checkpointAlgorithmElement);
} else if(elements.size() > 1){
parserContext.getReaderContext().error(
"The element may not appear more than once in a single <"
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java
index c54597bf1..bdee142e0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java
@@ -29,6 +29,7 @@ import org.w3c.dom.Element;
* parses a decision element and assumes that it refers to a {@link JobExecutionDecider}
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class DecisionParser {
@@ -37,7 +38,6 @@ public class DecisionParser {
private static final String REF_ATTRIBUTE = "ref";
public Collection parse(Element element, ParserContext parserContext) {
-
String refAttribute = element.getAttribute(REF_ATTRIBUTE);
String idAttribute = element.getAttribute(ID_ATTRIBUTE);
@@ -45,6 +45,9 @@ public class DecisionParser {
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.DecisionState");
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(idAttribute);
+
+ new PropertyParser(refAttribute, parserContext).parseProperties(element);
+
return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java
index 2d8829a37..d85a465a4 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java
@@ -17,9 +17,11 @@ package org.springframework.batch.core.jsr.configuration.xml;
import org.springframework.batch.core.configuration.xml.CoreNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@@ -28,14 +30,17 @@ import org.w3c.dom.Element;
* the standard Spring Batch artifacts.
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class JobParser extends AbstractSingleBeanDefinitionParser {
+ private static final String ID_ATTRIBUTE = "id";
+ private static final String RESTARTABLE_ATTRIBUTE = "restartable";
+ private static final String BATCH_PROPERTY_POST_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.jsr.configuration.support.BatchPropertyBeanPostProcessor";
+ private static final String BATCH_PROPERTY_POST_PROCESSOR_BEAN_NAME = "batchPropertyPostProcessor";
+ private static final String JSR_AUTOWIRED_ANNOTATION_BEAN_POST_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.jsr.configuration.support.JsrAutowiredAnnotationBeanPostProcessor";
- private static final String RESTARTABLE_ATTRIBUTE = "restartable";
- private static final String ID_ATTRIBUTE = "id";
-
- @Override
+ @Override
protected Class getBeanClass(Element element) {
return JobFactoryBean.class;
}
@@ -43,6 +48,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element));
+ autoregisterJsrBeansForNamespace(parserContext);
String jobName = element.getAttribute(ID_ATTRIBUTE);
builder.addConstructorArgValue(jobName);
@@ -56,5 +62,34 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
builder.addPropertyValue("flow", flowDef);
new ListnerParser(JobListenerFactoryBean.class, "jobExecutionListeners").parseListeners(element, parserContext, builder);
+ new PropertyParser("job-" + jobName, parserContext).parseProperties(element);
}
+
+ private void autoregisterJsrBeansForNamespace(ParserContext parserContext) {
+ autoRegisterBatchPostProcessor(parserContext);
+ autoRegisterJsrAutowiredAnnotationBeanPostProcessor(parserContext);
+ }
+
+ private void autoRegisterBatchPostProcessor(ParserContext parserContext) {
+ BeanDefinitionBuilder batchPropertyBeanPostProcessor =
+ BeanDefinitionBuilder.genericBeanDefinition(BATCH_PROPERTY_POST_PROCESSOR_CLASS_NAME);
+
+ AbstractBeanDefinition batchPropertyBeanPostProcessorDefinition = batchPropertyBeanPostProcessor.getBeanDefinition();
+ batchPropertyBeanPostProcessorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ parserContext.getRegistry().registerBeanDefinition(BATCH_PROPERTY_POST_PROCESSOR_BEAN_NAME, batchPropertyBeanPostProcessorDefinition);
+ }
+
+ private void autoRegisterJsrAutowiredAnnotationBeanPostProcessor(ParserContext parserContext) {
+ BeanDefinitionBuilder jsrAutowiredAnnotationBeanPostProcessor =
+ BeanDefinitionBuilder.genericBeanDefinition(JSR_AUTOWIRED_ANNOTATION_BEAN_POST_PROCESSOR_CLASS_NAME);
+
+ AbstractBeanDefinition jsrAutowiredAnnotationBeanPostProcessorDefinition =
+ jsrAutowiredAnnotationBeanPostProcessor.getBeanDefinition();
+
+ jsrAutowiredAnnotationBeanPostProcessorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ parserContext.getRegistry().registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
+ jsrAutowiredAnnotationBeanPostProcessorDefinition);
+ }
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java
index 64c6bab3d..809b7ebaa 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java
@@ -35,7 +35,6 @@ import org.w3c.dom.Element;
* @since 3.0
*/
public class ListnerParser {
-
private static final String REF_ATTRIBUTE = "ref";
private static final String LISTENER_ELEMENT = "listener";
private static final String LISTENERS_ELEMENT = "listeners";
@@ -43,11 +42,11 @@ public class ListnerParser {
private Class listenerType;
private String propertyKey;
- @SuppressWarnings("rawtypes")
+ @SuppressWarnings("rawtypes")
public ListnerParser(Class listenerType, String propertyKey) {
- this.listenerType = listenerType;
this.propertyKey = propertyKey;
- }
+ this.listenerType = listenerType;
+ }
public void parseListeners(Element element, ParserContext parserContext, AbstractBeanDefinition bd) {
ManagedList listeners = parseListeners(element, parserContext);
@@ -78,9 +77,14 @@ public class ListnerParser {
listeners.setMergeEnabled(false);
List listenerElements = DomUtils.getChildElementsByTagName(listenersElement, LISTENER_ELEMENT);
for (Element listenerElement : listenerElements) {
+ String beanName = listenerElement.getAttribute(REF_ATTRIBUTE);
+
BeanDefinitionBuilder bd = BeanDefinitionBuilder.genericBeanDefinition(listenerType);
- bd.addPropertyValue("delegate", new RuntimeBeanReference(listenerElement.getAttribute(REF_ATTRIBUTE)));
+ bd.addPropertyValue("delegate", new RuntimeBeanReference(beanName));
+
listeners.add(bd.getBeanDefinition());
+
+ new PropertyParser(beanName, parserContext).parseProperties(listenerElement);
}
parserContext.popAndRegisterContainingComponent();
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java
new file mode 100644
index 000000000..6477220dd
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 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.jsr.configuration.xml;
+
+import java.util.List;
+import java.util.Properties;
+import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Element;
+
+/**
+ *
+ * Parser for the <properties /> element defined by JSR-352.
+ *
+ * Parses <property> tag values from the provided {@link Element} if it contains a <properties /> element.
+ * Only one <properties /> element may be present. <property> elements have a name and value attribute
+ * which represent the property entries key and value.
+ *
+ *
+ * @param element
+ */
+ public void parseProperties(Element element) {
+ List propertiesElements = DomUtils.getChildElementsByTagName(element, PROPERTIES_ELEMENT);
+
+ Properties properties = new Properties();
+
+ if (propertiesElements.size() == 1) {
+ List propertyElements = DomUtils.getChildElementsByTagName(propertiesElements.get(0), PROPERTY_ELEMENT);
+
+ for (Element propertyElement : propertyElements) {
+ properties.put(propertyElement.getAttribute(PROPERTY_NAME_ATTRIBUTE), propertyElement.getAttribute(PROPERTY_VALUE_ATTRIBUTE));
+ }
+
+ addProperties(properties);
+ } else if (propertiesElements.size() > 1) {
+ parserContext.getReaderContext().error("The element may not appear more than once in a single .", element);
+ }
+ }
+
+ private void addProperties(Properties properties) {
+ BeanDefinition beanDefinition = parserContext.getRegistry().getBeanDefinition(BATCH_PROPERTY_CONTEXT_BEAN_NAME);
+
+ BatchPropertyContext batchPropertyContext = new BatchPropertyContext();
+ BatchPropertyContext.BatchPropertyContextEntry batchPropertyContextEntry =
+ batchPropertyContext.new BatchPropertyContextEntry(beanName, properties);
+
+ ManagedList managedList = new ManagedList();
+ managedList.setMergeEnabled(true);
+ managedList.add(batchPropertyContextEntry);
+
+ beanDefinition.getPropertyValues().addPropertyValue(BATCH_CONTEXT_ENTRIES_PROPERTY_NAME, managedList);
+ }
+
+ private void registerBatchPropertyContext() {
+ if (!parserContext.getRegistry().containsBeanDefinition(BATCH_PROPERTY_CONTEXT_BEAN_NAME)) {
+ BeanDefinitionBuilder batchPropertyContextBeanDefinitionBuilder =
+ BeanDefinitionBuilder.genericBeanDefinition(BATCH_PROPERTY_CONTEXT_BEAN_CLASS_NAME);
+
+ AbstractBeanDefinition batchPropertyContextBeanDefinition = batchPropertyContextBeanDefinitionBuilder.getBeanDefinition();
+ batchPropertyContextBeanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ parserContext.getRegistry().registerBeanDefinition(BATCH_PROPERTY_CONTEXT_BEAN_NAME, batchPropertyContextBeanDefinition);
+ }
+ }
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java
index 5473b9dcd..169ef8f24 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java
@@ -37,17 +37,17 @@ import org.springframework.util.StringUtils;
*
* @author Michael Minella
* @author Glenn Renfro
+ * @author Chris Schaefer
* @since 3.0
*/
public class StepParser extends AbstractSingleBeanDefinitionParser {
-
private static final String CHUNK_ELEMENT = "chunk";
private static final String BATCHLET_ELEMENT = "batchlet";
private static final String ALLOW_START_IF_COMPLETE_ATTRIBUTE = "allow-start-if-complete";
private static final String START_LIMIT_ATTRIBUTE = "start-limit";
private static final String SPLIT_ID_ATTRIBUTE = "id";
- protected Collection parse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
+ protected Collection parse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition bd = defBuilder.getRawBeanDefinition();
bd.setBeanClass(StepFactoryBean.class);
@@ -71,6 +71,7 @@ public class StepParser extends AbstractSingleBeanDefinitionParser {
}
new ListnerParser(StepListenerFactoryBean.class, "listeners").parseListeners(element, parserContext, bd);
+ new PropertyParser(stepName, parserContext).parseProperties(element);
// look at all nested elements
NodeList children = element.getChildNodes();
@@ -83,7 +84,7 @@ public class StepParser extends AbstractSingleBeanDefinitionParser {
String name = nestedElement.getLocalName();
if(name.equalsIgnoreCase(BATCHLET_ELEMENT)) {
- new BatchletParser().parseBatchlet(element, nestedElement, bd, parserContext);
+ new BatchletParser().parseBatchlet(nestedElement, bd, parserContext);
} else if(name.equals(CHUNK_ELEMENT)) {
new ChunkParser().parse(nestedElement, bd, parserContext);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
index 5f707e05d..bd41c04e8 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
@@ -45,16 +45,14 @@ import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.jsr.JobContext;
+import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
-import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
-import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.convert.converter.Converter;
@@ -114,9 +112,11 @@ import org.springframework.util.Assert;
* how job instances are identified differently.
*
* @author Michael Minella
+ * @author Chris Schaefer
* @since 3.0
*/
public class JsrJobOperator implements JobOperator {
+ private static final String BATCH_PROPERTY_CONTEXT_BEAN_NAME = "batchPropertyContext";
private org.springframework.batch.core.launch.JobOperator batchJobOperator;
private JobExplorer jobExplorer;
@@ -406,10 +406,8 @@ public class JsrJobOperator implements JobOperator {
}
batchContext.setParent(baseContext);
- GenericBeanDefinition bd = new GenericBeanDefinition();
- bd.setBeanClass(AutowiredAnnotationBeanPostProcessor.class);
- batchContext.registerBeanDefinition("postProcessor", bd);
batchContext.refresh();
+
final Job job = batchContext.getBean(Job.class);
if(!job.isRestartable()) {
@@ -426,8 +424,12 @@ public class JsrJobOperator implements JobOperator {
}
try {
- ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext)batchContext).getBeanFactory();
- factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, jobParametersConverter));
+ ConfigurableListableBeanFactory factory = batchContext.getBeanFactory();
+
+ BatchPropertyContext batchPropertyContext = factory.getBean(BATCH_PROPERTY_CONTEXT_BEAN_NAME, BatchPropertyContext.class);
+ Properties properties = batchPropertyContext.getBatchProperties("job-" + job.getName());
+
+ factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, properties));
taskExecutor.execute(new Runnable() {
@@ -485,9 +487,6 @@ public class JsrJobOperator implements JobOperator {
}
batchContext.setParent(baseContext);
- GenericBeanDefinition bd = new GenericBeanDefinition();
- bd.setBeanClass(AutowiredAnnotationBeanPostProcessor.class);
- batchContext.registerBeanDefinition("postProcessor", bd);
batchContext.refresh();
final Job job = batchContext.getBean(Job.class);
@@ -504,8 +503,12 @@ public class JsrJobOperator implements JobOperator {
}
try {
- ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext)batchContext).getBeanFactory();
- factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, jobParametersConverter));
+ ConfigurableListableBeanFactory factory = batchContext.getBeanFactory();
+
+ BatchPropertyContext batchPropertyContext = factory.getBean(BATCH_PROPERTY_CONTEXT_BEAN_NAME, BatchPropertyContext.class);
+ Properties properties = batchPropertyContext.getBatchProperties("job-" + job.getName());
+
+ factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, properties));
taskExecutor.execute(new Runnable() {
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java
index 51d7e0992..d01de801a 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/JobContextTests.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 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.jsr;
import static org.junit.Assert.assertEquals;
@@ -16,7 +31,6 @@ import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
-import org.springframework.batch.core.converter.JobParametersConverter;
public class JobContextTests {
@@ -25,13 +39,15 @@ public class JobContextTests {
private JobExecution execution;
@Mock
private JobInstance instance;
- @Mock
- private JobParametersConverter converter;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- context = new JobContext(execution, converter);
+
+ Properties properties = new Properties();
+ properties.put("jobLevelProperty1", "jobLevelValue1");
+
+ context = new JobContext(execution, properties);
when(execution.getJobInstance()).thenReturn(instance);
}
@@ -68,19 +84,19 @@ public class JobContextTests {
}
@Test
- public void testGetProperties() {
+ public void testJobParameters() {
JobParameters params = new JobParametersBuilder()
.addString("key1", "value1")
.toJobParameters();
- Properties results = new Properties();
- results.put("key1", "value1");
when(execution.getJobParameters()).thenReturn(params);
- when(converter.getProperties(params)).thenReturn(results);
- Properties props = context.getProperties();
+ assertEquals("value1", execution.getJobParameters().getString("key1"));
+ }
- assertEquals("value1", props.get("key1"));
+ @Test
+ public void testJobProperties() {
+ assertEquals("jobLevelValue1", context.getProperties().get("jobLevelProperty1"));
}
@Test
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java
index 217aab984..5873378b1 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/StepContextTests.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 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.jsr;
import static org.junit.Assert.assertEquals;
@@ -16,7 +31,6 @@ import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
-import org.springframework.batch.core.converter.JobParametersConverterSupport;
import org.springframework.batch.item.ExecutionContext;
public class StepContextTests {
@@ -44,7 +58,10 @@ public class StepContextTests {
executionContext = new ExecutionContext();
stepExecution.setExecutionContext(executionContext);
- stepContext = new StepContext(stepExecution, new JobParametersConverterSupport());
+ Properties properties = new Properties();
+ properties.put("key", "value");
+
+ stepContext = new StepContext(stepExecution, properties);
stepContext.setTransientUserData("This is my transient data");
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java
new file mode 100644
index 000000000..04493a01f
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright 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.jsr.configuration.xml;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Properties;
+import javax.batch.api.BatchProperty;
+import javax.batch.api.Batchlet;
+import javax.batch.api.chunk.ItemProcessor;
+import javax.batch.api.chunk.ItemReader;
+import javax.batch.api.chunk.ItemWriter;
+import javax.inject.Inject;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.aop.framework.Advised;
+import org.springframework.batch.core.ExitStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.core.job.flow.FlowExecutionStatus;
+import org.springframework.batch.core.job.flow.JobExecutionDecider;
+import org.springframework.batch.core.jsr.StepContext;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.scope.context.StepSynchronizationManager;
+import org.springframework.batch.repeat.CompletionPolicy;
+import org.springframework.batch.repeat.RepeatContext;
+import org.springframework.batch.repeat.RepeatStatus;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ *
+ * Test cases for parsing various <properties /> elements defined by JSR-352.
+ *