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 index 4bcc4c12d..6c4746f74 100644 --- 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 @@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; *

* * @author Chris Schaefer + * @author Michael Minella * @since 3.0 */ public class BatchPropertyContext { @@ -40,25 +41,25 @@ public class BatchPropertyContext { * * @param batchPropertyContextEntries the {@link BatchPropertyContextEntry} objects to add */ - public void setBatchContextEntries(List batchPropertyContextEntries) { - for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { - setBatchContextEntry(batchPropertyContextEntry); - } - } + public void setBatchContextEntries(List batchPropertyContextEntries) { + for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { + setBatchContextEntry(batchPropertyContextEntry); + } + } - private void setBatchContextEntry(BatchPropertyContextEntry batchPropertyContextEntry) { - String beanName = batchPropertyContextEntry.getBeanName(); - Properties properties = batchPropertyContextEntry.getProperties(); + private void setBatchContextEntry(BatchPropertyContextEntry batchPropertyContextEntry) { + String beanName = batchPropertyContextEntry.getBeanName(); + Properties properties = batchPropertyContextEntry.getProperties(); - if (batchProperties.containsKey(beanName)) { - Properties existingProperties = batchProperties.get(beanName); - existingProperties.putAll(properties); + if (batchProperties.containsKey(beanName)) { + Properties existingProperties = batchProperties.get(beanName); + existingProperties.putAll(properties); - batchProperties.put(beanName, existingProperties); - } else { - batchProperties.put(beanName, properties); - } - } + batchProperties.put(beanName, existingProperties); + } else { + batchProperties.put(beanName, properties); + } + } /** *

@@ -69,25 +70,33 @@ public class BatchPropertyContext { * @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(); + public Properties getBatchProperties(String beanName) { + Properties properties = new Properties(); - if (batchProperties.containsKey(beanName)) { - properties.putAll(batchProperties.get(beanName)); - } + if (batchProperties.containsKey(beanName)) { + properties.putAll(batchProperties.get(beanName)); + } else { + if(beanName.startsWith("scopedTarget")) { + beanName = beanName.substring(13); + } - for (String jobLevelProperty : batchProperties.keySet()) { - if (jobLevelProperty.startsWith("job-")) { - if (batchProperties.containsKey(jobLevelProperty)) { - properties.putAll(batchProperties.get(jobLevelProperty)); - } + if(batchProperties.containsKey(beanName)) { + properties.putAll(batchProperties.get(beanName)); + } + } - break; - } - } + for (String jobLevelProperty : batchProperties.keySet()) { + if (jobLevelProperty.startsWith("job-")) { + if (batchProperties.containsKey(jobLevelProperty)) { + properties.putAll(batchProperties.get(jobLevelProperty)); + } - return properties; - } + break; + } + } + + return properties; + } /** *

diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java new file mode 100644 index 000000000..95b8b0d8f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java @@ -0,0 +1,76 @@ +/* + * 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 org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.PriorityOrdered; + +/** + * After the {@link BeanFactory} is created, this post processor will evaluate to see + * if any of the beans referenced from a job definition (as defined by JSR-352) point + * to class names instead of bean names. If this is the case, a new {@link BeanDefinition} + * is added with the name of the class as the bean name. + * + * @author Michael Minella + * @since 3.0 + */ +public class ThreadLocalClassloaderBeanPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { + + /* (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) + */ + @Override + public void postProcessBeanFactory( + ConfigurableListableBeanFactory beanFactory) throws BeansException { + String [] beanNames = beanFactory.getBeanDefinitionNames(); + + for (String curName : beanNames) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(curName); + PropertyValue[] values = beanDefinition.getPropertyValues().getPropertyValues(); + + for (PropertyValue propertyValue : values) { + Object value = propertyValue.getValue(); + + if(value instanceof RuntimeBeanReference) { + RuntimeBeanReference ref = (RuntimeBeanReference) value; + if(!beanFactory.containsBean(ref.getBeanName())) { + AbstractBeanDefinition newBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(ref.getBeanName()).getBeanDefinition(); + newBeanDefinition.setScope("step"); + ((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(ref.getBeanName(), newBeanDefinition); + } + } + } + } + } + + /** + * Sets this {@link BeanFactoryPostProcessor} to the lowest precdenece so that + * it is executed as late as possible in the chain of {@link BeanFactoryPostProcessor}s + */ + @Override + public int getOrder() { + return PriorityOrdered.LOWEST_PRECEDENCE; + } +} 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 f511bff80..e1798ba08 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 @@ -22,7 +22,6 @@ 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; @@ -35,13 +34,10 @@ import org.w3c.dom.Element; * @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 ID_ATTRIBUTE = "id"; + private static final String RESTARTABLE_ATTRIBUTE = "restartable"; - @Override + @Override protected Class getBeanClass(Element element) { return JobFactoryBean.class; } @@ -49,7 +45,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element)); - autoregisterJsrBeansForNamespace(parserContext); + JsrNamespaceUtils.autoregisterJsrBeansForNamespace(parserContext); String jobName = element.getAttribute(ID_ATTRIBUTE); builder.addConstructorArgValue(jobName); @@ -73,31 +69,4 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { 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/JsrNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java new file mode 100644 index 000000000..9d4fbd5e7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java @@ -0,0 +1,67 @@ +/* + * 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 org.springframework.batch.core.jsr.configuration.support.BatchPropertyBeanPostProcessor; +import org.springframework.batch.core.jsr.configuration.support.JsrAutowiredAnnotationBeanPostProcessor; +import org.springframework.batch.core.jsr.configuration.support.ThreadLocalClassloaderBeanPostProcessor; +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.ParserContext; +import org.springframework.context.annotation.AnnotationConfigUtils; + +/** + * Utility methods used in parsing of the JSR-352 batch namespace + * + * @author Michael Minella + * @since 3.0 + */ +class JsrNamespaceUtils { + private static final String BATCH_PROPERTY_POST_PROCESSOR_BEAN_NAME = "batchPropertyPostProcessor"; + private static final String THREAD_LOCAL_CLASSLOASER_BEAN_POST_PROCESSOR_BEAN_NAME = "threadLocalClassloaderBeanPostProcessor"; + + static void autoregisterJsrBeansForNamespace(ParserContext parserContext) { + autoRegisterBatchPostProcessor(parserContext); + autoRegisterJsrAutowiredAnnotationBeanPostProcessor(parserContext); + autoRegisterThreadLocalClassloaderBeanPostProcessor(parserContext); + } + + private static void autoRegisterThreadLocalClassloaderBeanPostProcessor(ParserContext parserContext) { + registerPostProcessor(parserContext, ThreadLocalClassloaderBeanPostProcessor.class, BeanDefinition.ROLE_INFRASTRUCTURE, THREAD_LOCAL_CLASSLOASER_BEAN_POST_PROCESSOR_BEAN_NAME); + } + + private static void autoRegisterBatchPostProcessor(ParserContext parserContext) { + registerPostProcessor(parserContext, BatchPropertyBeanPostProcessor.class, BeanDefinition.ROLE_INFRASTRUCTURE, BATCH_PROPERTY_POST_PROCESSOR_BEAN_NAME); + } + + private static void autoRegisterJsrAutowiredAnnotationBeanPostProcessor(ParserContext parserContext) { + registerPostProcessor(parserContext, JsrAutowiredAnnotationBeanPostProcessor.class, BeanDefinition.ROLE_INFRASTRUCTURE, AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME); + } + + private static void registerPostProcessor(ParserContext parserContext, Class clazz, int role, String beanName) { + BeanDefinitionBuilder jsrAutowiredAnnotationBeanPostProcessor = + BeanDefinitionBuilder.genericBeanDefinition(clazz); + + AbstractBeanDefinition jsrAutowiredAnnotationBeanPostProcessorDefinition = + jsrAutowiredAnnotationBeanPostProcessor.getBeanDefinition(); + + jsrAutowiredAnnotationBeanPostProcessorDefinition.setRole(role); + + parserContext.getRegistry().registerBeanDefinition(beanName, + jsrAutowiredAnnotationBeanPostProcessorDefinition); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTests.java new file mode 100644 index 000000000..86cbee342 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTests.java @@ -0,0 +1,44 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import java.util.Properties; + +import javax.batch.operations.JobOperator; +import javax.batch.runtime.BatchRuntime; +import javax.batch.runtime.BatchStatus; + +import org.junit.Before; +import org.junit.Test; + +public class ThreadLocalClassloaderBeanPostProcessorTests { + + private JobOperator jobOperator; + + @Before + public void setUp() throws Exception { + jobOperator = BatchRuntime.getJobOperator(); + } + + @Test + public void test() throws Exception { + long executionId = jobOperator.start("threadLocalClassloaderBeanPostProcessorTestsJob", new Properties()); + + assertEquals(BatchStatus.COMPLETED, jobOperator.getJobExecution(executionId).getBatchStatus()); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTestsBatchlet.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTestsBatchlet.java new file mode 100644 index 000000000..3a2dc092b --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ThreadLocalClassloaderBeanPostProcessorTestsBatchlet.java @@ -0,0 +1,49 @@ +/* + * 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 javax.batch.api.BatchProperty; +import javax.batch.api.Batchlet; +import javax.batch.runtime.context.JobContext; +import javax.batch.runtime.context.StepContext; +import javax.inject.Inject; + +import org.springframework.util.Assert; + +public class ThreadLocalClassloaderBeanPostProcessorTestsBatchlet implements Batchlet { + @Inject + @BatchProperty + public String jobParam1; + + @Inject + public JobContext jobContext; + + @Inject + public StepContext stepContext; + + @Override + public String process() throws Exception { + Assert.isTrue("someParameter".equals(jobParam1), jobParam1 + " does not equal someParamter"); + Assert.isTrue("threadLocalClassloaderBeanPostProcessorTestsJob".equals(jobContext.getJobName())); + Assert.isTrue("step1".equals(stepContext.getStepName())); + + return null; + } + + @Override + public void stop() throws Exception { + } +} diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/threadLocalClassloaderBeanPostProcessorTestsJob.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/threadLocalClassloaderBeanPostProcessorTestsJob.xml new file mode 100644 index 000000000..e542a5d7a --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/threadLocalClassloaderBeanPostProcessorTestsJob.xml @@ -0,0 +1,10 @@ + + + + + + + + + +