OPEN - issue BATCH-282: Make input parameters easier to access from ItemReaders, etc.

Add StepScope and integrate with AbstractStep.  No support yet for late binding of step and job attributes.
This commit is contained in:
dsyer
2008-10-16 11:01:50 +00:00
parent e02092d78b
commit 9bf4241893
20 changed files with 1274 additions and 22 deletions

View File

@@ -0,0 +1,7 @@
package org.springframework.batch.core.scope;
public interface Collaborator {
public abstract String getName();
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2006-2007 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.scope;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.batch.repeat.RepeatContext;
/**
* @author Dave Syer
*
*/
public class StepContextRepeatCallbackTests {
@Test
public void testDoInIteration() throws Exception {
StepContext stepContext = new StepContext(new StepExecution("foo", new JobExecution(0L), 123L));
StepContextRepeatCallback callback = new StepContextRepeatCallback(stepContext ) {
@Override
public ExitStatus doInStepContext(RepeatContext context, StepContext stepContext) throws Exception {
assertEquals(Long.valueOf(123), stepContext.getStepExecution().getId());
return ExitStatus.NOOP;
}
};
assertEquals(ExitStatus.NOOP, callback.doInIteration(null));
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2006-2007 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.scope;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
/**
* @author Dave Syer
*
*/
public class StepContextTests {
private List<String> list = new ArrayList<String>();
private StepExecution stepExecution = new StepExecution("step", new JobExecution(0L));
private StepContext context = new StepContext(stepExecution);
@Test
public void testGetStepExecution() {
context = new StepContext(stepExecution);
assertNotNull(context.getStepExecution());
}
@Test
public void testNullStepExecution() {
try {
context = new StepContext(null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testEqualsSelf() {
assertEquals(context, context);
}
@Test
public void testNotEqualsNull() {
assertFalse(context.equals(null));
}
@Test
public void testEqualsContextWithSameStepExecution() {
assertEquals(new StepContext(stepExecution), context);
}
@Test
public void testDestructionCallbackSunnyDay() throws Exception {
context.setAttribute("foo", "FOO");
context.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("bar");
}
});
context.close();
assertEquals(1, list.size());
assertEquals("bar", list.get(0));
}
@Test
public void testDestructionCallbackMissingAttribute() throws Exception {
context.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("bar");
}
});
context.close();
// Yes the callback should be called even if the attribute is missing -
// for inner beans
assertEquals(1, list.size());
}
@Test
public void testDestructionCallbackWithException() throws Exception {
context.setAttribute("foo", "FOO");
context.setAttribute("bar", "BAR");
context.registerDestructionCallback("bar", new Runnable() {
public void run() {
list.add("spam");
throw new RuntimeException("fail!");
}
});
context.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("bar");
throw new RuntimeException("fail!");
}
});
try {
context.close();
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
// We don't care which one was thrown...
assertEquals("fail!", e.getMessage());
}
// ...but we do care that both were executed:
assertEquals(2, list.size());
assertTrue(list.contains("bar"));
assertTrue(list.contains("spam"));
}
}

View File

@@ -0,0 +1,73 @@
package org.springframework.batch.core.scope;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class StepScopeIntegrationTests {
@Autowired
@Qualifier("vanilla")
private Step vanilla;
@Autowired
@Qualifier("proxied")
private Step proxied;
@Autowired
@Qualifier("enhanced")
private Step enhanced;
@Before
@After
public void start() {
TestStep.reset();
}
@Test
public void testScopeCreation() throws Exception {
vanilla.execute(new StepExecution("foo",new JobExecution(11L)));
assertNotNull(TestStep.getContext());
assertNull(StepSynchronizationManager.getContext());
}
@Test
public void testScopedProxy() throws Exception {
proxied.execute(new StepExecution("foo",new JobExecution(11L)));
assertTrue(TestStep.getContext().attributeNames().length>0);
String collaborator = (String) TestStep.getContext().getAttribute("collaborator");
assertNotNull(collaborator);
assertEquals("bar", collaborator);
}
@Test
public void testExecutionContext() throws Exception {
StepExecution stepExecution = new StepExecution("foo",new JobExecution(11L));
ExecutionContext executionContext = new ExecutionContext();
executionContext.put("name", "spam");
stepExecution.setExecutionContext(executionContext);
enhanced.execute(stepExecution);
assertTrue(TestStep.getContext().attributeNames().length>0);
String collaborator = (String) TestStep.getContext().getAttribute("collaborator");
assertNotNull(collaborator);
assertEquals("bar", collaborator);
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2006-2007 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.scope;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.support.StaticApplicationContext;
/**
* @author Dave Syer
*
*/
public class StepScopeTests {
private StepScope scope = new StepScope();
private StepContext context = new StepContext(new StepExecution("foo", new JobExecution(0L)));
@Before
public void setUp() throws Exception {
StepSynchronizationManager.register(context);
}
@After
public void tearDown() throws Exception {
StepSynchronizationManager.close();
}
@Test
public void testGetWithNoContext() throws Exception {
final String foo = "bar";
StepSynchronizationManager.close();
try {
scope.get("foo", new ObjectFactory() {
public Object getObject() throws BeansException {
return foo;
}
});
fail("Expected IllegalStateException");
}
catch (IllegalStateException e) {
// expected
}
}
@Test
public void testGetWithNothingAlreadyThere() {
final String foo = "bar";
Object value = scope.get("foo", new ObjectFactory() {
public Object getObject() throws BeansException {
return foo;
}
});
assertEquals(foo, value);
assertTrue(context.hasAttribute("foo"));
}
@Test
public void testGetWithSomethingAlreadyThere() {
context.setAttribute("foo", "bar");
Object value = scope.get("foo", new ObjectFactory() {
public Object getObject() throws BeansException {
return null;
}
});
assertEquals("bar", value);
assertTrue(context.hasAttribute("foo"));
}
@Test
public void testGetWithSomethingAlreadyInParentContext() {
context.setAttribute("foo", "bar");
StepContext context = new StepContext(new StepExecution("bar", new JobExecution(0L)));
StepSynchronizationManager.register(context);
Object value = scope.get("foo", new ObjectFactory() {
public Object getObject() throws BeansException {
return "spam";
}
});
assertEquals("spam", value);
assertTrue(context.hasAttribute("foo"));
StepSynchronizationManager.close();
assertEquals("bar", scope.get("foo", null));
}
@Test
public void testGetConversationId() {
String id = scope.getConversationId();
assertNotNull(id);
}
@Test
public void testGetConversationIdFromAttribute() {
context.setAttribute(StepScope.ID_KEY, "foo");
String id = scope.getConversationId();
assertEquals("foo", id);
}
@Test
public void testRegisterDestructionCallback() {
final List<String> list = new ArrayList<String>();
context.setAttribute("foo", "bar");
scope.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("foo");
}
});
assertEquals(0, list.size());
// When the context is closed, provided the attribute exists the
// callback is called...
context.close();
assertEquals(1, list.size());
}
@Test
public void testRegisterAnotherDestructionCallback() {
final List<String> list = new ArrayList<String>();
context.setAttribute("foo", "bar");
scope.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("foo");
}
});
scope.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("bar");
}
});
assertEquals(0, list.size());
// When the context is closed, provided the attribute exists the
// callback is called...
context.close();
assertEquals(2, list.size());
}
@Test
public void testRemove() {
context.setAttribute("foo", "bar");
scope.remove("foo");
assertFalse(context.hasAttribute("foo"));
}
@Test
public void testOrder() throws Exception {
assertEquals(Integer.MAX_VALUE, scope.getOrder());
scope.setOrder(11);
assertEquals(11, scope.getOrder());
}
@Test
public void testName() throws Exception {
scope.setName("foo");
StaticApplicationContext beanFactory = new StaticApplicationContext();
scope.postProcessBeanFactory(beanFactory.getDefaultListableBeanFactory());
String[] scopes = beanFactory.getDefaultListableBeanFactory().getRegisteredScopeNames();
assertEquals(1, scopes.length);
assertEquals("foo", scopes[0]);
}
}

View File

@@ -0,0 +1,87 @@
package org.springframework.batch.core.scope;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
public class StepSynchronizationManagerTests {
private StepExecution stepExecution = new StepExecution("step", new JobExecution(0L));
@Before
@After
public void start() {
while (StepSynchronizationManager.getContext() != null) {
StepSynchronizationManager.close();
}
}
@Test
public void testGetContext() {
assertNull(StepSynchronizationManager.getContext());
StepSynchronizationManager.register(new StepContext(stepExecution));
assertNotNull(StepSynchronizationManager.getContext());
}
@Test
public void testClose() {
StepContext context = new StepContext(stepExecution);
final List<String> list = new ArrayList<String>();
context.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("foo");
}
});
StepSynchronizationManager.register(context);
StepSynchronizationManager.close();
assertNull(StepSynchronizationManager.getContext());
assertEquals(0, list.size());
}
@Test
public void testRelease() {
StepContext context = new StepContext(stepExecution);
final List<String> list = new ArrayList<String>();
context.registerDestructionCallback("foo", new Runnable() {
public void run() {
list.add("foo");
}
});
StepSynchronizationManager.register(context);
// On release we expect the destruction callbacks to be called
StepSynchronizationManager.release();
assertNull(StepSynchronizationManager.getContext());
assertEquals(1, list.size());
}
@Test
public void testRegisterNull() {
assertNull(StepSynchronizationManager.getContext());
StepSynchronizationManager.register(null);
assertNull(StepSynchronizationManager.getContext());
}
@Test
public void testRegisterTwice() {
StepContext context = new StepContext(stepExecution);
StepSynchronizationManager.register(context);
StepSynchronizationManager.register(context);
StepSynchronizationManager.close();
// if someone registers you have to assume they are going to close, so
// the last thing you want is for the close to remove another context
// that someone else has registered
assertNotNull(StepSynchronizationManager.getContext());
StepSynchronizationManager.close();
assertNull(StepSynchronizationManager.getContext());
}
}

View File

@@ -0,0 +1,19 @@
package org.springframework.batch.core.scope;
public class TestCollaborator implements Collaborator {
private String name;
/* (non-Javadoc)
* @see org.springframework.batch.core.scope.Collaborator#getName()
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,42 @@
package org.springframework.batch.core.scope;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
public class TestStep implements Step {
private static StepContext context;
private Collaborator collaborator;
public void setCollaborator(Collaborator collaborator) {
this.collaborator = collaborator;
}
public static StepContext getContext() {
return context;
}
public static void reset() {
context = null;
}
public void execute(StepExecution stepExecution) throws JobInterruptedException {
context = StepSynchronizationManager.getContext();
context.setAttribute("collaborator", collaborator.getName());
}
public String getName() {
return "foo";
}
public int getStartLimit() {
return Integer.MAX_VALUE;
}
public boolean isAllowStartIfComplete() {
return false;
}
}

View File

@@ -16,6 +16,7 @@ import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.scope.StepContext;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.ExitStatus;
import org.springframework.util.Assert;
@@ -54,8 +55,8 @@ public class AbstractStepTests {
events.add("open");
}
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
assertSame(execution, stepExecution);
protected ExitStatus doExecute(StepContext context) throws Exception {
assertSame(execution, context.getStepExecution());
events.add("doExecute");
return ExitStatus.FINISHED;
}
@@ -165,8 +166,9 @@ public class AbstractStepTests {
@Test
public void testFailure() throws Exception {
tested = new EventTrackingStep() {
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
super.doExecute(stepExecution);
@Override
protected ExitStatus doExecute(StepContext context) throws Exception {
super.doExecute(context);
throw new RuntimeException("crash!");
}
};
@@ -202,9 +204,10 @@ public class AbstractStepTests {
@Test
public void testStoppedStep() throws Exception {
tested = new EventTrackingStep() {
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
stepExecution.setTerminateOnly();
return super.doExecute(stepExecution);
@Override
protected ExitStatus doExecute(StepContext context) throws Exception {
context.getStepExecution().setTerminateOnly();
return super.doExecute(context);
}
};
tested.setJobRepository(repository);
@@ -237,8 +240,9 @@ public class AbstractStepTests {
@Test
public void testFailureInSavingExecutionContext() throws Exception {
tested = new EventTrackingStep() {
protected ExitStatus doExecute(StepExecution stepExecution) throws Exception {
super.doExecute(stepExecution);
@Override
protected ExitStatus doExecute(StepContext context) throws Exception {
super.doExecute(context);
return ExitStatus.FINISHED;
}
};