BATCH-2071: Implementation of JSR-352's requirement for loading batch artifacts via the thread local classloader
* Added a BeanFactoryPostProcessor to add bean definitions for batch artifacts referenced only by class name * Updated the BatchPropertyBeanPostProcessor to address scope targets * Moved the registration of the various post processors to a new JsrNamespaceUtils (similar to how they are handled with the core namespace)
This commit is contained in:
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* </p>
|
||||
*
|
||||
* @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<BatchPropertyContextEntry> batchPropertyContextEntries) {
|
||||
for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) {
|
||||
setBatchContextEntry(batchPropertyContextEntry);
|
||||
}
|
||||
}
|
||||
public void setBatchContextEntries(List<BatchPropertyContextEntry> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<JobFactoryBean> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<job id="threadLocalClassloaderBeanPostProcessorTestsJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
|
||||
<step id="step1" >
|
||||
<batchlet ref="org.springframework.batch.core.jsr.configuration.xml.ThreadLocalClassloaderBeanPostProcessorTestsBatchlet">
|
||||
<properties>
|
||||
<property name="jobParam1" value="someParameter"/>
|
||||
</properties>
|
||||
</batchlet>
|
||||
</step>
|
||||
</job>
|
||||
Reference in New Issue
Block a user