BATCH-2072: Implemented wrapper for JSR-352 Decider

This commit is contained in:
Michael Minella
2013-08-12 09:11:02 -05:00
parent aa73805a24
commit 3f381ddb89
6 changed files with 419 additions and 126 deletions

View File

@@ -0,0 +1,68 @@
/*
* 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 javax.batch.api.Decider;
import javax.batch.operations.BatchRuntimeException;
import org.springframework.batch.core.JobExecution;
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.util.Assert;
/**
* Wrapper for {@link Decider} implementation to allow it to be used
* by the rest of the framework.
*
* @author Michael Minella
* @since 3.0
*/
public class DecisionAdapter implements JobExecutionDecider {
private Decider decider;
/**
* @param decider a {@link Decider}
*/
public DecisionAdapter(Decider decider) {
Assert.notNull(decider, "A Decider implementation is required");
this.decider = decider;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.job.flow.JobExecutionDecider#decide(org.springframework.batch.core.JobExecution, org.springframework.batch.core.StepExecution)
*/
@Override
public FlowExecutionStatus decide(JobExecution jobExecution,
StepExecution stepExecution) {
javax.batch.runtime.StepExecution [] executions = null;
//TODO: Address splits
if(stepExecution != null) {
executions = new org.springframework.batch.core.jsr.StepExecution[1];
executions[0] = new org.springframework.batch.core.jsr.StepExecution(stepExecution);
}
try {
return new FlowExecutionStatus(decider.decide(executions));
} catch (Exception e) {
throw new BatchRuntimeException(e);
}
}
}

View File

@@ -21,20 +21,34 @@ 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.Decider;
import javax.batch.api.chunk.CheckpointAlgorithm;
import javax.batch.api.chunk.ItemProcessor;
import javax.batch.api.chunk.ItemReader;
import javax.batch.api.chunk.ItemWriter;
import javax.batch.api.chunk.listener.ChunkListener;
import javax.batch.api.chunk.listener.ItemProcessListener;
import javax.batch.api.chunk.listener.ItemReadListener;
import javax.batch.api.chunk.listener.ItemWriteListener;
import javax.batch.api.chunk.listener.RetryProcessListener;
import javax.batch.api.chunk.listener.RetryReadListener;
import javax.batch.api.chunk.listener.RetryWriteListener;
import javax.batch.api.chunk.listener.SkipProcessListener;
import javax.batch.api.chunk.listener.SkipReadListener;
import javax.batch.api.chunk.listener.SkipWriteListener;
import javax.batch.api.listener.JobListener;
import javax.batch.api.listener.StepListener;
import javax.batch.api.partition.PartitionAnalyzer;
import javax.batch.api.partition.PartitionCollector;
import javax.batch.api.partition.PartitionMapper;
import javax.batch.api.partition.PartitionPlan;
import javax.batch.api.partition.PartitionReducer;
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;
@@ -47,114 +61,129 @@ import org.springframework.util.ReflectionUtils;
* </p>
*
* @author Chris Schaefer
* @author Michael Minella
* @since 3.0
*/
public class BatchPropertyBeanPostProcessor implements BeanPostProcessor {
@Autowired
private BatchPropertyContext batchPropertyContext;
private Log logger = LogFactory.getLog(getClass());
private Set<Class<? extends Annotation>> requiredAnnotations = new HashSet<Class<? extends Annotation>>();
@Autowired
private BatchPropertyContext batchPropertyContext;
private Log logger = LogFactory.getLog(getClass());
private Set<Class<? extends Annotation>> requiredAnnotations = new HashSet<Class<? extends Annotation>>();
public BatchPropertyBeanPostProcessor() {
setRequiredAnnotations();
}
public BatchPropertyBeanPostProcessor() {
setRequiredAnnotations();
}
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
if (!isBatchArtifact(bean)) {
return bean;
}
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
if (!isBatchArtifact(bean)) {
return bean;
}
final Properties artifactProperties = batchPropertyContext.getBatchProperties(beanName);
final Properties artifactProperties = batchPropertyContext.getBatchProperties(beanName);
if (artifactProperties.isEmpty()) {
return bean;
}
if (artifactProperties.isEmpty()) {
return bean;
}
injectBatchProperties(bean, artifactProperties);
injectBatchProperties(bean, artifactProperties);
return bean;
}
return bean;
}
private void setRequiredAnnotations() {
ClassLoader cl = BatchPropertyBeanPostProcessor.class.getClassLoader();
@SuppressWarnings("unchecked")
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.");
}
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);
}
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 boolean isBatchArtifact(Object bean) {
return (bean instanceof ItemReader) ||
(bean instanceof ItemProcessor) ||
(bean instanceof ItemWriter) ||
(bean instanceof CheckpointAlgorithm) ||
(bean instanceof Batchlet) ||
(bean instanceof ItemReadListener) ||
(bean instanceof ItemProcessListener) ||
(bean instanceof ItemWriteListener) ||
(bean instanceof JobListener) ||
(bean instanceof StepListener) ||
(bean instanceof ChunkListener) ||
(bean instanceof SkipReadListener) ||
(bean instanceof SkipProcessListener) ||
(bean instanceof SkipWriteListener) ||
(bean instanceof RetryReadListener) ||
(bean instanceof RetryProcessListener) ||
(bean instanceof RetryWriteListener) ||
(bean instanceof PartitionMapper) ||
(bean instanceof PartitionReducer) ||
(bean instanceof PartitionCollector) ||
(bean instanceof PartitionAnalyzer) ||
(bean instanceof PartitionPlan) ||
(bean instanceof Decider);
}
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);
private void injectBatchProperties(final Object bean, final Properties artifactProperties) {
ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
@Override
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);
String batchProperty = getBatchPropertyFieldValue(field, artifactProperties);
if (batchProperty != null) {
field.set(bean, batchProperty);
}
if (batchProperty != null) {
field.set(bean, batchProperty);
}
field.setAccessible(isAccessible);
}
}
});
}
field.setAccessible(isAccessible);
}
}
});
}
private String getBatchPropertyFieldValue(Field field, Properties batchArtifactProperties) {
BatchProperty batchProperty = field.getAnnotation(BatchProperty.class);
private String getBatchPropertyFieldValue(Field field, Properties batchArtifactProperties) {
BatchProperty batchProperty = field.getAnnotation(BatchProperty.class);
if (!"".equals(batchProperty.name())) {
return getBatchProperty(batchProperty.name(), batchArtifactProperties);
}
if (!"".equals(batchProperty.name())) {
return getBatchProperty(batchProperty.name(), batchArtifactProperties);
}
return getBatchProperty(field.getName(), batchArtifactProperties);
}
return getBatchProperty(field.getName(), batchArtifactProperties);
}
private String getBatchProperty(String propertyKey, Properties batchArtifactProperties) {
if (batchArtifactProperties.containsKey(propertyKey)) {
return (String) batchArtifactProperties.get(propertyKey);
}
private String getBatchProperty(String propertyKey, Properties batchArtifactProperties) {
if (batchArtifactProperties.containsKey(propertyKey)) {
return (String) batchArtifactProperties.get(propertyKey);
}
return null;
}
return null;
}
private boolean isAnnotated(Field field) {
for (Class<? extends Annotation> annotation : requiredAnnotations) {
if(!field.isAnnotationPresent(annotation)) {
return false;
}
}
private boolean isAnnotated(Field field) {
for (Class<? extends Annotation> annotation : requiredAnnotations) {
if(!field.isAnnotationPresent(annotation)) {
return false;
}
}
return true;
}
return true;
}
private boolean isValidFieldModifier(Field field) {
return !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers());
}
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;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

View File

@@ -19,7 +19,6 @@ import java.util.Collection;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
@@ -27,7 +26,7 @@ import org.w3c.dom.Element;
/**
* Parser for the &lt;decision /&gt; element as specified in JSR-352. The current state
* parses a decision element and assumes that it refers to a {@link JobExecutionDecider}
*
*
* @author Michael Minella
* @author Chris Schaefer
* @since 3.0
@@ -42,9 +41,9 @@ public class DecisionParser {
String idAttribute = element.getAttribute(ID_ATTRIBUTE);
BeanDefinitionBuilder stateBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.DecisionState");
stateBuilder.addConstructorArgValue(new RuntimeBeanReference(refAttribute));
stateBuilder.addConstructorArgValue(idAttribute);
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.configuration.xml.DecisionStateFactoryBean");
stateBuilder.addPropertyReference("decider", refAttribute);
stateBuilder.addPropertyValue("name", idAttribute);
new PropertyParser(refAttribute, parserContext).parseProperties(element);

View File

@@ -0,0 +1,102 @@
/*
* 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.Decider;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.State;
import org.springframework.batch.core.job.flow.support.state.DecisionState;
import org.springframework.batch.core.jsr.DecisionAdapter;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* {@link FactoryBean} for creating a {@link DecisionState}. If the underlying
* decider is a {@link Decider}, it will be wrapped in a {@link DecisionAdapter}.
*
* @author Michael Minella
* @since 3.0
*/
public class DecisionStateFactoryBean implements FactoryBean<State>, InitializingBean {
private Decider jsrDecider;
private JobExecutionDecider decider;
private String name;
/**
* @param decider either a {@link Decider} or a {@link JobExecutionDecider}
* @throws IllegalArgumentException if the type passed in is not a valid type
*/
public void setDecider(Object decider) {
if(decider instanceof JobExecutionDecider) {
this.decider = (JobExecutionDecider) decider;
} else if(decider instanceof Decider) {
this.jsrDecider = (Decider) decider;
} else {
throw new IllegalArgumentException("Invalid type for a decider");
}
}
/**
* The name of the state
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@Override
public State getObject() throws Exception {
JobExecutionDecider wrappedDecider = null;
if(decider != null) {
wrappedDecider = decider;
} else if(jsrDecider != null) {
wrappedDecider = new DecisionAdapter(jsrDecider);
}
State state = new DecisionState(wrappedDecider, name);
return state;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return JobExecutionDecider.class;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.isTrue(!(decider == null && jsrDecider == null), "A decider implementation is required");
Assert.notNull(name, "A name is required for a decision state");
}
}

View File

@@ -0,0 +1,100 @@
package org.springframework.batch.core.jsr.configuration.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import javax.batch.api.Decider;
import javax.batch.runtime.StepExecution;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.State;
import org.springframework.batch.core.job.flow.support.state.DecisionState;
public class DecisionStateFactoryBeanTests {
private DecisionStateFactoryBean factoryBean;
@Before
public void setUp() throws Exception {
factoryBean = new DecisionStateFactoryBean();
}
@Test
public void testGetObjectType() {
assertEquals(JobExecutionDecider.class, factoryBean.getObjectType());
}
@Test
public void testIsSingleton() {
assertTrue(factoryBean.isSingleton());
}
@Test(expected=IllegalArgumentException.class)
public void testNullDeciderAndName() throws Exception {
factoryBean.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void testNullDecider() throws Exception{
factoryBean.setName("state1");
factoryBean.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void testNullName() throws Exception {
factoryBean.setDecider(new DeciderSupport());
factoryBean.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void setWrongDeciderType() {
factoryBean.setDecider("Some decider");
}
@Test
public void testJobExecutionDeciderState() throws Exception {
factoryBean.setDecider(new JobExecutionDeciderSupport());
factoryBean.setName("IL");
factoryBean.afterPropertiesSet();
State state = factoryBean.getObject();
assertEquals("IL", state.getName());
assertEquals(DecisionState.class, state.getClass());
}
@Test
public void testDeciderDeciderState() throws Exception {
factoryBean.setDecider(new DeciderSupport());
factoryBean.setName("IL");
factoryBean.afterPropertiesSet();
State state = factoryBean.getObject();
assertEquals("IL", state.getName());
assertEquals(DecisionState.class, state.getClass());
}
public static class DeciderSupport implements Decider {
@Override
public String decide(StepExecution[] executions) throws Exception {
return null;
}
}
public static class JobExecutionDeciderSupport implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution,
org.springframework.batch.core.StepExecution stepExecution) {
return null;
}
}
}

View File

@@ -24,6 +24,8 @@ import java.util.List;
import javax.batch.api.BatchProperty;
import javax.batch.api.Batchlet;
import javax.batch.api.Decider;
import javax.batch.api.chunk.CheckpointAlgorithm;
import javax.batch.api.chunk.ItemProcessor;
import javax.batch.api.chunk.ItemReader;
import javax.batch.api.chunk.ItemWriter;
@@ -37,13 +39,7 @@ 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.launch.JobLauncher;
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;
@@ -335,32 +331,13 @@ public class JobPropertyTests {
}
}
public static final class TestCheckpointAlgorithm implements CompletionPolicy {
public static final class TestCheckpointAlgorithm implements CheckpointAlgorithm {
@Inject @BatchProperty String algorithmPropertyName1;
@Inject @BatchProperty String algorithmPropertyName2;
@Inject @BatchProperty(name = "annotationNamedAlgorithmPropertyName") String annotationNamedProperty;
@Inject @BatchProperty String notDefinedProperty;
@Inject @BatchProperty(name = "notDefinedAnnotationNamedProperty") String notDefinedAnnotationNamedProperty;
@Override
public boolean isComplete(RepeatContext context, RepeatStatus result) {
return true;
}
@Override
public boolean isComplete(RepeatContext context) {
return true;
}
@Override
public RepeatContext start(RepeatContext parent) {
return parent;
}
@Override
public void update(RepeatContext context) {
}
String getAlgorithmPropertyName1() {
return algorithmPropertyName1;
}
@@ -380,21 +357,33 @@ public class JobPropertyTests {
String getNotDefinedAnnotationNamedProperty() {
return notDefinedAnnotationNamedProperty;
}
@Override
public int checkpointTimeout() throws Exception {
return 0;
}
@Override
public void beginCheckpoint() throws Exception {
}
@Override
public boolean isReadyToCheckpoint() throws Exception {
return true;
}
@Override
public void endCheckpoint() throws Exception {
}
}
public static class TestDecider implements JobExecutionDecider {
public static class TestDecider implements Decider {
@Inject @BatchProperty String deciderPropertyName1;
@Inject @BatchProperty String deciderPropertyName2;
@Inject @BatchProperty(name = "annotationNamedDeciderPropertyName") String annotationNamedProperty;
@Inject @BatchProperty String notDefinedProperty;
@Inject @BatchProperty(name = "notDefinedAnnotationNamedProperty") String notDefinedAnnotationNamedProperty;
@Override
public FlowExecutionStatus decide(JobExecution jobExecution,
StepExecution stepExecution) {
return new FlowExecutionStatus("step2");
}
String getDeciderPropertyName1() {
return deciderPropertyName1;
}
@@ -414,6 +403,12 @@ public class JobPropertyTests {
String getNotDefinedAnnotationNamedProperty() {
return notDefinedAnnotationNamedProperty;
}
@Override
public String decide(javax.batch.runtime.StepExecution[] executions)
throws Exception {
return "step2";
}
}
public static class TestStepListener implements javax.batch.api.chunk.listener.ItemReadListener,