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:
Michael Minella
2013-08-13 21:27:09 -05:00
parent 3f381ddb89
commit d06ebaedc3
7 changed files with 290 additions and 66 deletions

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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 {
}
}

View File

@@ -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>