diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java index 8965434ce..73549227b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SimpleFlowFactoryBean.java @@ -1,10 +1,14 @@ package org.springframework.batch.core.configuration.xml; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; +import org.springframework.batch.core.job.flow.FlowHolder; import org.springframework.batch.core.job.flow.State; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; @@ -68,7 +72,8 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { List updatedTransitions = new ArrayList(); for (StateTransition stateTransition : stateTransitions) { State state = getProxyState(stateTransition.getState()); - updatedTransitions.add(StateTransition.switchOriginAndDestination(stateTransition, state, getNext(stateTransition.getNext()))); + updatedTransitions.add(StateTransition.switchOriginAndDestination(stateTransition, state, + getNext(stateTransition.getNext()))); } flow.setStateTransitions(updatedTransitions); @@ -119,7 +124,7 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { * @author Dave Syer * */ - private static class DelegateState extends AbstractState { + private static class DelegateState extends AbstractState implements FlowHolder { private final State state; private DelegateState(String name, State state) { @@ -135,6 +140,11 @@ public class SimpleFlowFactoryBean implements FactoryBean, InitializingBean { public FlowExecutionStatus handle(FlowExecutor executor) throws Exception { return state.handle(executor); } + + public Collection getFlows() { + return (state instanceof FlowHolder) ? ((FlowHolder)state).getFlows() : Collections.emptyList(); + } + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowHolder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowHolder.java new file mode 100644 index 000000000..3c3555820 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowHolder.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006-2010 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.job.flow; + +import java.util.Collection; + +/** + * Convenient interface for components that contain nested flows. + * + * @author Dave Syer + * + */ +public interface FlowHolder { + + Collection getFlows(); + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java index 2845f32c7..596512b4a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java @@ -16,7 +16,8 @@ package org.springframework.batch.core.job.flow; import java.util.Collection; -import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; @@ -25,6 +26,7 @@ import org.springframework.batch.core.Step; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.SimpleStepHandler; import org.springframework.batch.core.step.StepHolder; +import org.springframework.batch.core.step.StepLocator; /** * Implementation of the {@link Job} interface that allows for complex flows of @@ -39,6 +41,10 @@ public class FlowJob extends AbstractJob { private Flow flow; + private Map stepMap = new ConcurrentHashMap(); + + private volatile boolean initialized = false; + /** * Create a {@link FlowJob} with null name and no flow (invalid state). */ @@ -67,23 +73,44 @@ public class FlowJob extends AbstractJob { */ @Override public Step getStep(String stepName) { - State state = this.flow.getState(stepName); - if (state instanceof StepHolder) { - Step step = ((StepHolder) state).getStep(); - if (stepName.equals(step.getName())) { - return step; + if (!initialized) { + init(); + } + return stepMap.get(stepName); + } + + /** + * Initialize the step names + */ + private void init() { + findSteps(flow, stepMap); + } + + /** + * @param flow + * @param map + */ + private void findSteps(Flow flow, Map map) { + + for (State state : flow.getStates()) { + if (state instanceof StepHolder) { + Step step = ((StepHolder) state).getStep(); + String name = step.getName(); + stepMap.put(name, step); + } + else if (state instanceof FlowHolder) { + for (Flow subflow : ((FlowHolder) state).getFlows()) { + findSteps(subflow, map); + } + } + else if (state instanceof StepLocator) { + StepLocator locator = (StepLocator) state; + for (String name : locator.getStepNames()) { + map.put(name, locator.getStep(name)); + } } } - // The state names can be prefixed with the job name for - // uniqueness... - state = this.flow.getState(getName() + "." + stepName); - if (state instanceof StepHolder) { - Step step = ((StepHolder) state).getStep(); - if (stepName.equals(step.getName())) { - return step; - } - } - return null; + } /** @@ -91,14 +118,10 @@ public class FlowJob extends AbstractJob { */ @Override public Collection getStepNames() { - Collection steps = new HashSet(); - for (State state : flow.getStates()) { - if (state instanceof StepHolder) { - String name = ((StepHolder) state).getStep().getName(); - steps.add(name); - } + if (!initialized) { + init(); } - return steps; + return stepMap.keySet(); } /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/FlowState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/FlowState.java index c173e5d33..1f8f141a4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/FlowState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/FlowState.java @@ -16,9 +16,13 @@ package org.springframework.batch.core.job.flow.support.state; +import java.util.Collection; +import java.util.Collections; + import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; +import org.springframework.batch.core.job.flow.FlowHolder; /** * State that delegates to a Flow @@ -26,7 +30,7 @@ import org.springframework.batch.core.job.flow.FlowExecutor; * @author Dave Syer * @since 2.0 */ -public class FlowState extends AbstractState { +public class FlowState extends AbstractState implements FlowHolder { private final Flow flow; @@ -37,6 +41,13 @@ public class FlowState extends AbstractState { super(name); this.flow = flow; } + + /** + * @return the flows + */ + public Collection getFlows() { + return Collections.singleton(flow); + } @Override public FlowExecutionStatus handle(FlowExecutor executor) throws Exception { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java index 340f9a0a5..887b611a7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/state/SplitState.java @@ -27,6 +27,7 @@ import org.springframework.batch.core.job.flow.FlowExecution; import org.springframework.batch.core.job.flow.FlowExecutionException; import org.springframework.batch.core.job.flow.FlowExecutionStatus; import org.springframework.batch.core.job.flow.FlowExecutor; +import org.springframework.batch.core.job.flow.FlowHolder; import org.springframework.batch.core.job.flow.State; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; @@ -39,7 +40,7 @@ import org.springframework.core.task.TaskRejectedException; * @author Dave Syer * @since 2.0 */ -public class SplitState extends AbstractState { +public class SplitState extends AbstractState implements FlowHolder { private final Collection flows; @@ -62,6 +63,13 @@ public class SplitState extends AbstractState { public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } + + /** + * @return the flows + */ + public Collection getFlows() { + return flows; + } /** * Execute the flows in parallel by passing them to the {@link TaskExecutor} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java index 85d0ea8c9..1f0d942be 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/SplitJobParserTests.java @@ -18,6 +18,9 @@ package org.springframework.batch.core.configuration.xml; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.ArrayList; +import java.util.Collections; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.batch.core.BatchStatus; @@ -25,6 +28,7 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.StepLocator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; @@ -53,6 +57,9 @@ public class SplitJobParserTests { job.execute(jobExecution); assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); assertEquals(4, jobExecution.getStepExecutions().size()); + ArrayList names = new ArrayList(((StepLocator)job).getStepNames()); + Collections.sort(names); + assertEquals("[s1, s2, s3, s4]", names.toString()); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java index 87af2a1c6..d9a8e5364 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepNameTests.java @@ -16,6 +16,7 @@ package org.springframework.batch.core.configuration.xml; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Collection; @@ -66,6 +67,7 @@ public class StepNameTests { Collection stepNames = stepLocator.getStepNames(); Job job = (Job) context.getBean(name); String jobName = job.getName(); + assertTrue("Job has no steps: "+jobName, !stepNames.isEmpty()); for (String registeredName : stepNames) { String stepName = stepLocator.getStep(registeredName).getName(); assertEquals("Step name not equal to registered value: " + stepName + "!=" + registeredName + ", " + jobName, diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java index 834665621..584eb7f96 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/FlowJobTests.java @@ -22,6 +22,7 @@ import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Before; @@ -38,6 +39,7 @@ import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.job.flow.support.StateTransition; import org.springframework.batch.core.job.flow.support.state.DecisionState; import org.springframework.batch.core.job.flow.support.state.EndState; +import org.springframework.batch.core.job.flow.support.state.FlowState; import org.springframework.batch.core.job.flow.support.state.SplitState; import org.springframework.batch.core.job.flow.support.state.StepState; import org.springframework.batch.core.repository.JobRepository; @@ -582,6 +584,62 @@ public class FlowJobTests { assertNull(step); } + @Test + public void testGetStepNestedFlow() throws Exception { + SimpleFlow nested = new SimpleFlow("nested"); + List transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), "end1")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1"))); + nested.setStateTransitions(transitions); + nested.afterPropertiesSet(); + + SimpleFlow flow = new SimpleFlow("job"); + transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "nested")); + transitions.add(StateTransition.createStateTransition(new FlowState(nested, "nested"), "end0")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0"))); + flow.setStateTransitions(transitions); + flow.afterPropertiesSet(); + job.setFlow(flow); + job.afterPropertiesSet(); + + List names = new ArrayList(job.getStepNames()); + Collections.sort(names); + assertEquals("[step1, step2]", names.toString()); + } + + @Test + public void testGetStepSplitFlow() throws Exception { + SimpleFlow flow = new SimpleFlow("job"); + SimpleFlow flow1 = new SimpleFlow("flow1"); + SimpleFlow flow2 = new SimpleFlow("flow2"); + + List transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step1")), "end0")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end0"))); + flow1.setStateTransitions(new ArrayList(transitions)); + flow1.afterPropertiesSet(); + transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new StepState(new StubStep("step2")), "end1")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end1"))); + flow2.setStateTransitions(new ArrayList(transitions)); + flow2.afterPropertiesSet(); + + transitions = new ArrayList(); + transitions.add(StateTransition.createStateTransition(new SplitState(Arrays. asList(flow1, flow2), + "split"), "end2")); + transitions.add(StateTransition.createEndStateTransition(new EndState(FlowExecutionStatus.COMPLETED, "end2"))); + flow.setStateTransitions(transitions); + flow.afterPropertiesSet(); + + job.setFlow(flow); + job.afterPropertiesSet(); + List names = new ArrayList(job.getStepNames()); + Collections.sort(names); + assertEquals("[step1, step2]", names.toString()); + } + + /** /** * @author Dave Syer *