From 35c9e2ea899499bc3b34ffc913e1246ee9ab29b0 Mon Sep 17 00:00:00 2001 From: Michael Minella Date: Sun, 4 Aug 2013 19:08:56 -0500 Subject: [PATCH] BATCH-2006: Completed implementation of JobContext and StepContext * Implemented the interfaces per the JSR-352 spec * Added a ne StepContextFactoryBean used to add the StepContext (one per step) into the step scope to be available for injection by batch artifacts. * Updated the StepParser to utilize the new StepContextFactoryBean. --- .../batch/core/jsr/StepContext.java | 68 ++++++++++++++++- .../core/jsr/StepContextFactoryBean.java | 73 +++++++++++++++++++ .../jsr/configuration/xml/StepParser.java | 10 ++- .../batch/core/jsr/StepContextTests.java | 40 ++++++++++ 4 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java 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 50c107ee7..f57a8ce22 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 @@ -16,6 +16,7 @@ package org.springframework.batch.core.jsr; import java.io.Serializable; +import java.util.List; import java.util.Properties; import javax.batch.runtime.BatchStatus; @@ -26,6 +27,14 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.util.Assert; +/** + * Wrapper class to provide the {@link javax.batch.runtime.context.StepContext} functionality + * as specified in JSR-352. Wrapper delegates to the underlying {@link StepExecution} to + * obtain the related contextual information. + * + * @author Michael Minella + * @since 3.0 + */ public class StepContext implements javax.batch.runtime.context.StepContext { private StepExecution stepExecution; @@ -40,61 +49,116 @@ public class StepContext implements javax.batch.runtime.context.StepContext { this.jobParametersConveter = jobParametersConveter; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getStepName() + */ @Override public String getStepName() { return stepExecution.getStepName(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getTransientUserData() + */ @Override public Object getTransientUserData() { return transientUserData; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#setTransientUserData(java.lang.Object) + */ @Override public void setTransientUserData(Object data) { this.transientUserData = data; } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getStepExecutionId() + */ @Override public long getStepExecutionId() { return stepExecution.getId(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getProperties() + */ @Override public Properties getProperties() { + //TODO: Fix this...this should be properties, not parameters. Waiting on BATCH-2001 return jobParametersConveter.getProperties(this.stepExecution.getJobParameters()); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getPersistentUserData() + */ @Override public Serializable getPersistentUserData() { - return null; + return (Serializable) stepExecution.getExecutionContext().get("batch_jsr_persistentUserData"); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#setPersistentUserData(java.io.Serializable) + */ @Override public void setPersistentUserData(Serializable data) { stepExecution.getExecutionContext().put("batch_jsr_persistentUserData", data); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getBatchStatus() + */ @Override public BatchStatus getBatchStatus() { return stepExecution.getStatus().getBatchStatus(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getExitStatus() + */ @Override public String getExitStatus() { return stepExecution.getExitStatus().getExitCode(); } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#setExitStatus(java.lang.String) + */ @Override public void setExitStatus(String status) { stepExecution.setExitStatus(new ExitStatus(status)); } + /** + * To support both JSR-352's requirement to return the most recent exception + * and Spring Batch's support for {@link Throwable}, this implementation will + * return the most recent exception in the underlying {@link StepExecution}'s + * failure exceptions list. If the exception there extends {@link Throwable} + * instead of {@link Exception}, it will be wrapped in an {@link Exception} and + * then returned. + * + * @see javax.batch.runtime.context.StepContext#getException() + */ @Override public Exception getException() { - return null; + List failureExceptions = stepExecution.getFailureExceptions(); + if(failureExceptions == null || failureExceptions.isEmpty()) { + return null; + } else { + Throwable t = failureExceptions.get(failureExceptions.size() - 1); + + if(t instanceof Exception) { + return (Exception) t; + } else { + return new Exception(t); + } + } } + /* (non-Javadoc) + * @see javax.batch.runtime.context.StepContext#getMetrics() + */ @Override public Metric[] getMetrics() { Metric[] metrics = new Metric[8]; 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 new file mode 100644 index 000000000..da05c3ecb --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java @@ -0,0 +1,73 @@ +/* + * 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.sql.DataSource; + +import org.springframework.batch.core.converter.JobParametersConverter; +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 + * @since 3.0 + */ +public class StepContextFactoryBean implements FactoryBean, InitializingBean { + + @Autowired + public DataSource dataSource; + private JobParametersConverter jobParametersConveter; + + /* (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() + */ + @Override + public StepContext getObject() throws Exception { + return new StepContext(StepSynchronizationManager.getContext().getStepExecution(), jobParametersConveter); + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + public Class getObjectType() { + return StepContext.class; + } + + /* (non-Javadoc) + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + @Override + public boolean isSingleton() { + return false; + } +} 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 e26407363..11794c03c 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 @@ -18,6 +18,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.Collection; import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.jsr.StepContextFactoryBean; import org.springframework.batch.core.listener.StepListenerFactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; @@ -32,7 +33,7 @@ import org.w3c.dom.NodeList; /** * Parser for the <step /> element defined by JSR-352. - * + * * @author Michael Minella * @since 3.0 */ @@ -86,6 +87,13 @@ public class StepParser extends AbstractSingleBeanDefinitionParser { } } + AbstractBeanDefinition stepContextBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(StepContextFactoryBean.class) + .getBeanDefinition(); + + stepContextBeanDefinition.setScope("step"); + + parserContext.getRegistry().registerBeanDefinition(stepName + "stepContext", stepContextBeanDefinition); + return FlowParser.getNextElements(parserContext, stepName, stateBuilder.getBeanDefinition(), element); } } 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 9a8498e5a..217aab984 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,6 +1,8 @@ package org.springframework.batch.core.jsr; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Properties; @@ -15,11 +17,13 @@ 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 { private StepExecution stepExecution; private StepContext stepContext; + private ExecutionContext executionContext; @Before public void setUp() throws Exception { @@ -37,6 +41,8 @@ public class StepContextTests { stepExecution.setRollbackCount(6); stepExecution.setWriteCount(7); stepExecution.setWriteSkipCount(8); + executionContext = new ExecutionContext(); + stepExecution.setExecutionContext(executionContext); stepContext = new StepContext(stepExecution, new JobParametersConverterSupport()); stepContext.setTransientUserData("This is my transient data"); @@ -92,4 +98,38 @@ public class StepContextTests { stepContext.setExitStatus("new Exit Status"); assertEquals("new Exit Status", stepExecution.getExitStatus().getExitCode()); } + + @Test + public void testPersistentUserData() { + String data = "saved data"; + stepContext.setPersistentUserData(data); + assertEquals(data, stepContext.getPersistentUserData()); + assertEquals(data, executionContext.get("batch_jsr_persistentUserData")); + } + + @Test + public void testGetExceptionEmpty() { + assertNull(stepContext.getException()); + } + + @Test + public void testGetExceptionException() { + stepExecution.addFailureException(new Exception("expected")); + assertEquals("expected", stepContext.getException().getMessage()); + } + + @Test + public void testGetExceptionThrowable() { + stepExecution.addFailureException(new Throwable("expected")); + assertTrue(stepContext.getException().getMessage().endsWith("expected")); + } + + @Test + public void testGetExceptionMultiple() { + stepExecution.addFailureException(new Exception("not me")); + stepExecution.addFailureException(new Exception("not me either")); + stepExecution.addFailureException(new Exception("me")); + + assertEquals("me", stepContext.getException().getMessage()); + } }