Update ref guide for new XML features

This commit is contained in:
dsyer
2010-01-04 14:30:49 +00:00
parent bfd6fccb7d
commit 5da092e0e2
2 changed files with 339 additions and 276 deletions

View File

@@ -4,8 +4,9 @@
<chapter id="configureJob">
<title>Configuring and Running a Job</title>
<para>In <xref linkend="domain" /> , the overall architecture design was
discussed, using the following diagram as a guide:</para>
<para>In the <link linkend="domain">domain section</link> , the
overall architecture design was discussed, using the following
diagram as a guide:</para>
<mediaobject>
<imageobject role="html">
@@ -35,7 +36,7 @@
<classname>Job</classname> </link> interface, however, the namespace
abstracts away the differences in configuration. It has only three
required dependencies: a name, <classname>JobRepository</classname> , and
a list of <classname>Step</classname> s.</para>
a list of <classname>Step</classname>s.</para>
<programlisting>&lt;job id="footballJob"&gt;
&lt;step id="playerload" parent="s1" next="gameLoad"/&gt;
@@ -43,9 +44,11 @@
&lt;step id="playerSummarization" parent="s3"/&gt;
&lt;/job&gt;</programlisting>
<para>The namespace defaults to referencing a repository with an id of
'jobRepository', which is a sensible default. However, this can be
overridden explicitly:</para>
<para>The examples here use a parent bean definition to create the steps;
see the section on <link linkend="configureStep">step configuration</link>
for more options declaring specific step details inline. The XML namespace
defaults to referencing a repository with an id of 'jobRepository', which
is a sensible default. However, this can be overridden explicitly:</para>
<programlisting>&lt;job id="footballJob" <emphasis role="bold">job-repository="specialRepository"</emphasis>&gt;
&lt;step id="playerload" parent="s1" next="gameLoad"/&gt;
@@ -53,6 +56,12 @@
&lt;step id="playerSummarization" parent="s3"/&gt;
&lt;/job&gt;</programlisting>
<para>In addition to steps a job configuration can contain other elements
that help with parallelisation (<literal>&lt;split/&gt;</literal>),
declarative flow control (<literal>&lt;decision/&gt;</literal>) and
externalization of flow definitions
(<literal>&lt;flow/&gt;</literal>).</para>
<section id="restartability">
<title>Restartability</title>
@@ -194,31 +203,26 @@ catch (JobRestartException e) {
for more detailed information.</para>
</section>
<section id="jobFactoryAndStatefulComponentsInSteps">
<title>JobFactory and Stateful Components in Steps</title>
<section>
<title>JobParametersValidator</title>
<para>Unlike many traditional Spring applications, many of the
components of a batch application are stateful; the file readers and
writers are obvious examples. The recommended way to deal with this is
to create a fresh <classname>ApplicationContext</classname> for each job
execution. If the <classname>Job</classname> is launched from the
command line with <classname>CommandLineJobRunner</classname>, this is
trivial. For more complex launching scenarios where jobs are executed in
parallel or serially from the same process, some extra steps have to be
taken to ensure that the <classname>ApplicationContext</classname> is
refreshed. This is preferable to using prototype scope for the stateful
beans because then they would not receive lifecycle callbacks from the
container at the end of use. (e.g. through destroy-method in XML)</para>
<para>A job declared in the XML namespace or using any subclass of
AbstractJob can optionally declare a validator for the job parameters at
runtime. This is useful when for instance you need to assert that a job
is started with all its mandatory parameters. There is a
DefaultJobParametersValidator that can be used to constrain combinations
of simple mandatory and optional parameters, and for more complex
constraints you can implement the interface yourself. The configuration
of a validator is supported through the XML namespace through a child
element of the job, e.g&gt;:</para>
<para>The strategy provided by Spring Batch to deal with this scenario
is the <classname>JobFactory</classname>, and the samples provide an
example of a specialized implementation that can load an
<classname>ApplicationContext</classname> and close it properly when the
job is finished. A relevant examples is
<classname>ClassPathXmlApplicationContextJobFactory</classname> and its
use in the <code>adhoc-job-launcher-context.xml</code> and the
<code>quartz-job-launcher-context.xml</code>, which can be found in the
Samples project.</para>
<programlisting>&lt;job id="job1" parent="baseJob3"&gt;
&lt;step id="step1" parent="standaloneStep"/&gt;
&lt;validator ref="paremetersValidator"/&gt;
&lt;/job&gt;</programlisting>
<para>The validator can be specified as a reference (as above) or as a
nested bean definition in the beans namespace.</para>
</section>
</section>
@@ -537,11 +541,13 @@ catch (JobRestartException e) {
<screen><prompt>bash$</prompt> java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date(date)=2008/01/01</screen>
<para>In most cases you would want to use a manifest to declare your
main class in a jar, but for simplicity, the class was used directly.
This example is using the same 'EndOfDay' example from <xref
linkend="domain" />. The first argument is 'endOfDayJob.xml', which is
the Spring <classname>ApplicationContext</classname> containing the
<para>In most cases you would want to use a manifest to
declare your main class in a jar, but for simplicity, the
class was used directly. This example is using the same
'EndOfDay' example from the <link linkend="domain">domain
section</link>. The first argument is 'endOfDayJob.xml', which
is the Spring <classname>ApplicationContext</classname>
containing the
<classname>Job</classname>. The second argument, 'endOfDay' represents
the job name. The final argument, 'schedule.date(date)=2008/01/01'
will be converted into <classname>JobParameters</classname>. An

View File

@@ -54,13 +54,13 @@
<para>Below is a code representation of the same concepts shown
above:</para>
<programlisting><![CDATA[List items = new Arraylist();
for(int i = 0; i < commitInterval; i++){
<programlisting>List items = new Arraylist();
for(int i = 0; i &lt; commitInterval; i++){
Object item = itemReader.read()
Object processedItem = itemProcessor.process(item);
items.add(processedItem);
}
itemWriter.write(items);]]></programlisting>
itemWriter.write(items);</programlisting>
<section id="configuringAStep">
<title>Configuring a Step</title>
@@ -70,13 +70,13 @@ itemWriter.write(items);]]></programlisting>
potentially contain many collaborators. In order to ease configuration,
the Spring Batch namespace can be used:</para>
<programlisting><![CDATA[<job id="sampleJob" job-repository="jobRepository">
<step id="step1">
<tasklet transaction-manager="transactionManager">
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
<tasklet>
</step>
</job>]]></programlisting>
<programlisting>&lt;job id="sampleJob" job-repository="jobRepository"&gt;
&lt;step id="step1"&gt;
&lt;tasklet transaction-manager="transactionManager"&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="10"/&gt;
&lt;tasklet&gt;
&lt;/step&gt;
&lt;/job&gt;</programlisting>
<para>The configuration above represents the only required dependencies
to create a item-oriented step:<itemizedlist>
@@ -121,43 +121,6 @@ itemWriter.write(items);]]></programlisting>
writer.</para>
</section>
<section id="standaloneStep">
<title>Referencing a Standalone Step</title>
<para>While steps must exist within a Job to define the flow, it can
sometimes be useful to reference a 'standalone' Step. For example, if a
Step is used by multiple jobs it can be useful to declare it once and
reference it from multiple jobs. This can be achieved with the 'parent'
attribute:</para>
<programlisting>&lt;job id="sampleJob" job-repository="jobRepository"&gt;
&lt;step id="step1" <emphasis role="bold">parent="standaloneStep"</emphasis> /&gt;
&lt;/job&gt;
&lt;step id="standaloneStep"&gt;
&lt;tasklet job-repository="jobRepository" transaction-manager="transactionManager"&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="10"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
<para>It should be noted that the id attribute is still required on the
step within the job element. This is for two reasons:</para>
<orderedlist>
<listitem>
<para>The id will be used as the step name when persisting the
StepExecution. If the same standalone step is referenced in more
than one step in the job, an error will occur.</para>
</listitem>
<listitem>
<para>When creating job flows, as described later in this chapter,
the next attribute should be referring to the step in the flow, not
the standalone step.</para>
</listitem>
</orderedlist>
</section>
<section id="InheritingFromParentStep">
<title>Inheriting from a Parent Step</title>
@@ -175,17 +138,32 @@ itemWriter.write(items);]]></programlisting>
allowStartIfComplete=true. Additionally, the commitInterval will be '5'
since it is overridden by the "concreteStep1":</para>
<programlisting><![CDATA[<step id="parentStep">
<tasklet allow-start-if-complete="true">
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
</tasklet>
</step>
<programlisting>&lt;step id="parentStep"&gt;
&lt;tasklet allow-start-if-complete="true"&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="10"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;
<step id="concreteStep1" parent="parentStep">
<tasklet start-limit="5">
<chunk processor="itemProcessor" commit-interval="5"/>
</tasklet>
</step>]]></programlisting>
&lt;step id="concreteStep1" parent="parentStep"&gt;
&lt;tasklet start-limit="5"&gt;
&lt;chunk processor="itemProcessor" commit-interval="5"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
<para>The id attribute is still required on the step within the job
element. This is for two reasons:<orderedlist>
<listitem>
<para>The id will be used as the step name when persisting the
StepExecution. If the same standalone step is referenced in more
than one step in the job, an error will occur.</para>
</listitem>
<listitem>
<para>When creating job flows, as described later in this chapter,
the next attribute should be referring to the step in the flow,
not the standalone step.</para>
</listitem>
</orderedlist></para>
<section id="abstractStep">
<title>Abstract Step</title>
@@ -205,17 +183,17 @@ itemWriter.write(items);]]></programlisting>
be abstract. The <classname>Step</classname> "concreteStep2" will have
'itemReader', 'itemWriter', and commitInterval=10.</para>
<programlisting><![CDATA[<step id="abstractParentStep" abstract="true">
<tasklet>
<chunk commit-interval="10"/>
</tasklet>
</step>
<programlisting>&lt;step id="abstractParentStep" abstract="true"&gt;
&lt;tasklet&gt;
&lt;chunk commit-interval="10"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;
<step id="concreteStep2" parent="abstractParentStep">
<tasklet>
<chunk reader="itemReader" writer="itemWriter"/>
</tasklet>
</step>]]></programlisting>
&lt;step id="concreteStep2" parent="abstractParentStep"&gt;
&lt;tasklet&gt;
&lt;chunk reader="itemReader" writer="itemWriter"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
</section>
<section id="mergingListsOnStep">
@@ -236,20 +214,20 @@ itemWriter.write(items);]]></programlisting>
<classname>listenerOne</classname> and
<classname>listenerTwo</classname>:</para>
<programlisting><![CDATA[<step id="listenersParentStep" abstract="true">
<listeners>
<listener ref="listenerOne"/>
<listeners>
</step>
<programlisting>&lt;step id="listenersParentStep" abstract="true"&gt;
&lt;listeners&gt;
&lt;listener ref="listenerOne"/&gt;
&lt;listeners&gt;
&lt;/step&gt;
<step id="concreteStep3" parent="listenersParentStep">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" commit-interval="5"/>
<listeners merge="true">
<listener ref="listenerTwo"/>
<listeners>
</tasklet>
</step>]]></programlisting>
&lt;step id="concreteStep3" parent="listenersParentStep"&gt;
&lt;tasklet&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="5"/&gt;
&lt;listeners merge="true"&gt;
&lt;listener ref="listenerTwo"/&gt;
&lt;listeners&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
</section>
</section>
@@ -341,26 +319,26 @@ itemWriter.write(items);]]></programlisting>
<section id="stepRestartExample">
<title>Step Restart Configuration Example</title>
<programlisting><![CDATA[<job id="footballJob" restartable="true">
<step id="playerload" next="gameLoad">
<tasklet>
<chunk reader="playerFileItemReader" writer="playerWriter"
commit-interval="10" />
</tasklet>
</step>
<step id="gameLoad" next="playerSummarization">
<tasklet allow-start-if-complete="true">
<chunk reader="gameFileItemReader" writer="gameWriter"
commit-interval="10"/>
</tasklet>
</step>
<step id="playerSummarization">
<tasklet start-limit="3">
<chunk reader="playerSummarizationSource" writer="summaryWriter"
commit-interval="10"/>
</tasklet>
</step>
</job>]]></programlisting>
<programlisting>&lt;job id="footballJob" restartable="true"&gt;
&lt;step id="playerload" next="gameLoad"&gt;
&lt;tasklet&gt;
&lt;chunk reader="playerFileItemReader" writer="playerWriter"
commit-interval="10" /&gt;
&lt;/tasklet&gt;
&lt;/step&gt;
&lt;step id="gameLoad" next="playerSummarization"&gt;
&lt;tasklet allow-start-if-complete="true"&gt;
&lt;chunk reader="gameFileItemReader" writer="gameWriter"
commit-interval="10"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;
&lt;step id="playerSummarization"&gt;
&lt;tasklet start-limit="3"&gt;
&lt;chunk reader="playerSummarizationSource" writer="summaryWriter"
commit-interval="10"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;
&lt;/job&gt;</programlisting>
<para>The above example configuration is for a job that loads in
information about football games and summarizes them. It contains
@@ -570,14 +548,14 @@ itemWriter.write(items);]]></programlisting>
the <classname>Step</classname> can be configured with a list of
exceptions that should not cause rollback.</para>
<programlisting><![CDATA[<step id="step1">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
<no-rollback-exception-classes>
<include class="org.springframework.batch.item.validator.ValidationException"/>
</no-rollback-exception-classes>
</tasklet>
</step>]]></programlisting>
<programlisting>&lt;step id="step1"&gt;
&lt;tasklet&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="2"/&gt;
&lt;no-rollback-exception-classes&gt;
&lt;include class="org.springframework.batch.item.validator.ValidationException"/&gt;
&lt;/no-rollback-exception-classes&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
<section id="transactionalReaders">
<title>Transactional Readers</title>
@@ -609,14 +587,14 @@ itemWriter.write(items);]]></programlisting>
transaction attributes can be found in the spring core
documentation.</para>
<programlisting><![CDATA[<step id="step1">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
<transaction-attributes isolation="DEFAULT"
<programlisting>&lt;step id="step1"&gt;
&lt;tasklet&gt;
&lt;chunk reader="itemReader" writer="itemWriter" commit-interval="2"/&gt;
&lt;transaction-attributes isolation="DEFAULT"
propagation="REQUIRED"
timeout="30"/>
</tasklet>
</step>]]></programlisting>
timeout="30"/&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
</section>
<section id="registeringItemStreams">
@@ -689,14 +667,14 @@ itemWriter.write(items);]]></programlisting>
interface (or an extension thereof) can be applied to a step via the
listeners element:</para>
<programlisting><![CDATA[<step id="step1">
<tasklet>
<chunk reader="reader" writer="writer" commit-interval="10"/>
<listeners>
<listener ref="stepListener"/>
</listeners>
</tasklet>
</step>]]></programlisting>
<programlisting>&lt;step id="step1"&gt;
&lt;tasklet&gt;
&lt;chunk reader="reader" writer="writer" commit-interval="10"/&gt;
&lt;listeners&gt;
&lt;listener ref="stepListener"/&gt;
&lt;/listeners&gt;
&lt;/tasklet&gt;
&lt;/step&gt;</programlisting>
<para>An <classname>ItemReader</classname>,
<classname>ItemWriter</classname> or
@@ -728,13 +706,13 @@ itemWriter.write(items);]]></programlisting>
for notification before a <classname>Step</classname> is started and
after it has ends, whether it ended normally or failed:</para>
<programlisting><![CDATA[public interface StepExecutionListener extends StepListener {
<programlisting>public interface StepExecutionListener extends StepListener {
void beforeStep(StepExecution stepExecution);
ExitStatus afterStep(StepExecution stepExecution);
}]]></programlisting>
}</programlisting>
<para><classname>ExitStatus</classname> is the return type of
<methodname>afterStep</methodname> in order to allow listeners the
@@ -763,13 +741,13 @@ itemWriter.write(items);]]></programlisting>
useful to perform logic before a chunk begins processing or after a
chunk has completed:</para>
<programlisting><![CDATA[public interface ChunkListener extends StepListener {
<programlisting>public interface ChunkListener extends StepListener {
void beforeChunk();
void afterChunk();
}]]></programlisting>
}</programlisting>
<para>The <methodname>beforeChunk</methodname> method is called after
the transaction is started, but before <methodname>read</methodname>
@@ -798,7 +776,7 @@ itemWriter.write(items);]]></programlisting>
<para>When discussing skip logic above, it was mentioned that it may
be beneficial to log out skipped records, so that they can be deal
with later. In the case of read errors, this can be done with an
<classname>ItemReaderListener:</classname><programlisting><![CDATA[public interface ItemReadListener<T> extends StepListener {
<classname>ItemReaderListener:</classname><programlisting>public interface ItemReadListener&lt;T&gt; extends StepListener {
void beforeRead();
@@ -806,7 +784,7 @@ itemWriter.write(items);]]></programlisting>
void onReadError(Exception ex);
}]]></programlisting></para>
}</programlisting></para>
<para>The <methodname>beforeRead</methodname> method will be called
before each call to <methodname>read</methodname> on the
@@ -841,7 +819,7 @@ itemWriter.write(items);]]></programlisting>
<para>Just as with the <classname>ItemReadListener</classname>, the
processing of an item can be 'listened' to:</para>
<programlisting><![CDATA[public interface ItemProcessListener<T, S> extends StepListener {
<programlisting>public interface ItemProcessListener&lt;T, S&gt; extends StepListener {
void beforeProcess(T item);
@@ -849,7 +827,7 @@ itemWriter.write(items);]]></programlisting>
void onProcessError(T item, Exception e);
}]]></programlisting>
}</programlisting>
<para>The <methodname>beforeProcess</methodname> method will be called
before <methodname>process</methodname> on the
@@ -884,15 +862,15 @@ itemWriter.write(items);]]></programlisting>
<para>The writing of an item can be 'listened' to with the
<classname>ItemWriteListener</classname>:</para>
<programlisting><![CDATA[ public interface ItemWriteListener<S> extends StepListener {
<programlisting> public interface ItemWriteListener&lt;S&gt; extends StepListener {
void beforeWrite(List<? extends S> items);
void beforeWrite(List&lt;? extends S&gt; items);
void afterWrite(List<? extends S> items);
void afterWrite(List&lt;? extends S&gt; items);
void onWriteError(Exception exception, List<? extends S> items);
void onWriteError(Exception exception, List&lt;? extends S&gt; items);
}]]></programlisting>
}</programlisting>
<para>The <methodname>beforeWrite</methodname> method will be called
before <methodname>write</methodname> on the
@@ -932,8 +910,8 @@ itemWriter.write(items);]]></programlisting>
this reason, there is a separate interface for tracking skipped
items:</para>
<programlisting><![CDATA[
public interface SkipListener<T,S> extends StepListener {
<programlisting>
public interface SkipListener&lt;T,S&gt; extends StepListener {
void onSkipInRead(Throwable t);
@@ -941,7 +919,7 @@ itemWriter.write(items);]]></programlisting>
void onSkipInWrite(S item, Throwable t);
}]]></programlisting>
}</programlisting>
<para><methodname>onSkipInRead</methodname> will be called whenever an
item is skipped while reading. It should be noted that rollbacks may
@@ -1042,12 +1020,12 @@ itemWriter.write(items);]]></programlisting>
this class without having to write an adapter for the
<classname>Tasklet</classname> interface:</para>
<programlisting><![CDATA[<bean id="myTasklet" class="org.springframework.batch.core.step.tasklet.TaskletAdapter">
<property name="targetObject">
<bean class="org.mycompany.FooDao">
</property>
<property name="targetMethod" value="updateFoo" />
</bean>]]></programlisting>
<programlisting>&lt;bean id="myTasklet" class="org.springframework.batch.core.step.tasklet.TaskletAdapter"&gt;
&lt;property name="targetObject"&gt;
&lt;bean class="org.mycompany.FooDao"&gt;
&lt;/property&gt;
&lt;property name="targetMethod" value="updateFoo" /&gt;
&lt;/bean&gt;</programlisting>
</section>
<section id="exampleTaskletImplementation">
@@ -1062,7 +1040,7 @@ itemWriter.write(items);]]></programlisting>
project, is a <classname>Tasklet</classname> implementation with just
such a responsibility:</para>
<programlisting><![CDATA[public class FileDeletingTasklet implements Tasklet, InitializingBean {
<programlisting>public class FileDeletingTasklet implements Tasklet, InitializingBean {
private Resource directory;
@@ -1072,7 +1050,7 @@ itemWriter.write(items);]]></programlisting>
Assert.state(dir.isDirectory());
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
for (int i = 0; i &lt; files.length; i++) {
boolean deleted = files[i].delete();
if (!deleted) {
throw new UnexpectedJobExecutionException("Could not delete file " +
@@ -1089,7 +1067,7 @@ itemWriter.write(items);]]></programlisting>
public void afterPropertiesSet() throws Exception {
Assert.notNull(directory, "directory must be set");
}
}]]></programlisting>
}</programlisting>
<para>The above <classname>Tasklet</classname> implementation will
delete all files within a given directory. It should be noted that the
@@ -1097,21 +1075,21 @@ itemWriter.write(items);]]></programlisting>
that is left is to reference the <classname>Tasklet</classname> from the
<classname>Step</classname>:</para>
<programlisting><![CDATA[<job id="taskletJob">
<step id="deleteFilesInDir">
<tasklet ref="fileDeletingTasklet"/>
</step>
</job>
<programlisting>&lt;job id="taskletJob"&gt;
&lt;step id="deleteFilesInDir"&gt;
&lt;tasklet ref="fileDeletingTasklet"/&gt;
&lt;/step&gt;
&lt;/job&gt;
<beans:bean id="fileDeletingTasklet"
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
<beans:property name="directoryResource">
<beans:bean id="directory"
class="org.springframework.core.io.FileSystemResource">
<beans:constructor-arg value="target/test-outputs/test-dir" />
</beans:bean>
</beans:property>
</beans:bean>]]></programlisting>
&lt;beans:bean id="fileDeletingTasklet"
class="org.springframework.batch.sample.tasklet.FileDeletingTasklet"&gt;
&lt;beans:property name="directoryResource"&gt;
&lt;beans:bean id="directory"
class="org.springframework.core.io.FileSystemResource"&gt;
&lt;beans:constructor-arg value="target/test-outputs/test-dir" /&gt;
&lt;/beans:bean&gt;
&lt;/beans:property&gt;
&lt;/beans:bean&gt;</programlisting>
</section>
</section>
@@ -1148,15 +1126,15 @@ itemWriter.write(items);]]></programlisting>
<para>This can be achieved using the 'next' attribute of the step
element:</para>
<para><programlisting><![CDATA[<job id="job">
<step id="stepA" parent="s1" next="stepB" />
<step id="stepB" parent="s2" next="stepC"/>
<step id="stepC" parent="s3" />
</job>]]></programlisting>In the scenario above, 'step A' will execute first
because it is the first <classname>Step</classname> listed. If 'step A'
completes normally, then 'step B' will execute, and so on. However, if
'step A' fails, then the entire <classname>Job</classname> will fail and
'step B' will not execute.</para>
<para><programlisting>&lt;job id="job"&gt;
&lt;step id="stepA" parent="s1" next="stepB" /&gt;
&lt;step id="stepB" parent="s2" next="stepC"/&gt;
&lt;step id="stepC" parent="s3" /&gt;
&lt;/job&gt;</programlisting>In the scenario above, 'step A' will execute
first because it is the first <classname>Step</classname> listed. If
'step A' completes normally, then 'step B' will execute, and so on.
However, if 'step A' fails, then the entire <classname>Job</classname>
will fail and 'step B' will not execute.</para>
<note>
<para>With the Spring Batch namespace, the first step listed in the
@@ -1214,14 +1192,14 @@ itemWriter.write(items);]]></programlisting>
<para>The next element specifies a pattern to match and the step to
execute next:</para>
<para><programlisting><![CDATA[<job id="job">
<step id="stepA" parent="s1">
<next on="*" to="stepB" />
<next on="FAILED" to="stepC" />
</step>
<step id="stepB" parent="s2" next="stepC" />
<step id="stepC" parent="s3" />
</job>]]></programlisting></para>
<para><programlisting>&lt;job id="job"&gt;
&lt;step id="stepA" parent="s1"&gt;
&lt;next on="*" to="stepB" /&gt;
&lt;next on="FAILED" to="stepC" /&gt;
&lt;/step&gt;
&lt;step id="stepB" parent="s2" next="stepC" /&gt;
&lt;step id="stepC" parent="s3" /&gt;
&lt;/job&gt;</programlisting></para>
<para>The "on" attribute of a transition element uses a simple
pattern-matching scheme to match the <classname>ExitStatus</classname>
@@ -1269,7 +1247,7 @@ itemWriter.write(items);]]></programlisting>
it fails, and so on. The example above contains the following 'next'
element:</para>
<programlisting><![CDATA[<next on="FAILED" to="stepB" />]]></programlisting>
<programlisting>&lt;next on="FAILED" to="stepB" /&gt;</programlisting>
<para>At first glance, it would appear that the 'on' attribute
references the <classname>BatchStatus</classname> of the
@@ -1286,11 +1264,11 @@ itemWriter.write(items);]]></programlisting>
code needs to be different? A good example comes from the skip sample
job within the samples project:</para>
<programlisting><![CDATA[<step id="step1" parent="s1">
<end on="FAILED" />
<next on="COMPLETED WITH SKIPS" to="errorPrint1" />
<next on="*" to="step2" />
</step>]]></programlisting>
<programlisting>&lt;step id="step1" parent="s1"&gt;
&lt;end on="FAILED" /&gt;
&lt;next on="COMPLETED WITH SKIPS" to="errorPrint1" /&gt;
&lt;next on="*" to="step2" /&gt;
&lt;/step&gt;</programlisting>
<para>The above step has three possibilities:</para>
@@ -1316,12 +1294,12 @@ itemWriter.write(items);]]></programlisting>
change the exit code based on the condition of the execution having
skipped records:</para>
<programlisting><![CDATA[public class SkipCheckingListener extends StepExecutionListenerSupport {
<programlisting>public class SkipCheckingListener extends StepExecutionListenerSupport {
public ExitStatus afterStep(StepExecution stepExecution) {
String exitCode = stepExecution.getExitStatus().getExitCode();
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
stepExecution.getSkipCount() > 0) {
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &amp;&amp;
stepExecution.getSkipCount() &gt; 0) {
return new ExitStatus("COMPLETED WITH SKIPS");
}
else {
@@ -1329,7 +1307,7 @@ itemWriter.write(items);]]></programlisting>
}
}
}]]></programlisting>
}</programlisting>
<para>The above code is a <classname>StepExecutionListener</classname>
that first checks to make sure the <classname>Step</classname> was
@@ -1357,7 +1335,7 @@ itemWriter.write(items);]]></programlisting>
after the following step executes, the <classname>Job</classname> will
end:</para>
<para><programlisting><![CDATA[<step id="stepC" parent="s3"/>]]></programlisting></para>
<para><programlisting>&lt;step id="stepC" parent="s3"/&gt;</programlisting></para>
<para>If no transitions are defined for a <classname>Step</classname>,
then the <classname>Job</classname>'s statuses will be defined as
@@ -1416,14 +1394,14 @@ itemWriter.write(items);]]></programlisting>
fails, the <classname>Job</classname> will not be restartable (because
the status is COMPLETED).</para>
<programlisting><![CDATA[<step id="step1" parent="s1" next="step2">
<programlisting>&lt;step id="step1" parent="s1" next="step2"&gt;
<step id="step2" parent="s2">
<end on="FAILED"/>
<next on="*" to="step3"/>
</step>
&lt;step id="step2" parent="s2"&gt;
&lt;end on="FAILED"/&gt;
&lt;next on="*" to="step3"/&gt;
&lt;/step&gt;
<step id="step3" parent="s3">]]></programlisting>
&lt;step id="step3" parent="s3"&gt;</programlisting>
</section>
<section id="failElement">
@@ -1447,14 +1425,14 @@ itemWriter.write(items);]]></programlisting>
Additionally, if step2 fails, and the <classname>Job</classname> is
restarted, then execution will begin again on step2.</para>
<programlisting><![CDATA[<step id="step1" parent="s1" next="step2">
<programlisting>&lt;step id="step1" parent="s1" next="step2"&gt;
<step id="step2" parent="s2">
<fail on="FAILED" exit-code="EARLY TERMINATION"/>
<next on="*" to="step3"/>
</step>
&lt;step id="step2" parent="s2"&gt;
&lt;fail on="FAILED" exit-code="EARLY TERMINATION"/&gt;
&lt;next on="*" to="step3"/&gt;
&lt;/step&gt;
<step id="step3" parent="s3">]]></programlisting>
&lt;step id="step3" parent="s3"&gt;</programlisting>
</section>
<section id="stopElement">
@@ -1472,11 +1450,11 @@ itemWriter.write(items);]]></programlisting>
the job will then stop. Once it is restarted, execution will begin on
step2.</para>
<para><programlisting><![CDATA[<step id="step1" parent="s1">
<stop on="COMPLETED" restart="step2"/>
</step>
<para><programlisting>&lt;step id="step1" parent="s1"&gt;
&lt;stop on="COMPLETED" restart="step2"/&gt;
&lt;/step&gt;
<step id="step2" parent="s2"/>]]></programlisting></para>
&lt;step id="step2" parent="s2"/&gt;</programlisting></para>
</section>
</section>
@@ -1489,7 +1467,7 @@ itemWriter.write(items);]]></programlisting>
<classname>JobExecutionDecider</classname> can be used to assist in the
decision.</para>
<para><programlisting><![CDATA[public class MyDecider implements JobExecutionDecider {
<para><programlisting>public class MyDecider implements JobExecutionDecider {
public String decide(JobExecution jobExecution, StepExecution stepExecution) {
if (someCondition) {
return "FAILED";
@@ -1498,24 +1476,24 @@ itemWriter.write(items);]]></programlisting>
return "COMPLETED";
}
}
}]]></programlisting></para>
}</programlisting></para>
<para>In the job configuration, a "decision" tag will specify the
decider to use as well as all of the transitions.</para>
<para><programlisting><![CDATA[<job id="job">
<step id="step1" parent="s1" next="decision" />
<para><programlisting>&lt;job id="job"&gt;
&lt;step id="step1" parent="s1" next="decision" /&gt;
<decision id="decision" decider="decider">
<next on="FAILED" to="step2" />
<next on="COMPLETED" to="step3" />
</decision>
&lt;decision id="decision" decider="decider"&gt;
&lt;next on="FAILED" to="step2" /&gt;
&lt;next on="COMPLETED" to="step3" /&gt;
&lt;/decision&gt;
<step id="step2" parent="s2" next="step3"/>
<step id="step3" parent="s3" />
</job>
&lt;step id="step2" parent="s2" next="step3"/&gt;
&lt;step id="step3" parent="s3" /&gt;
&lt;/job&gt;
<beans:bean id="decider" class="com.MyDecider"/>]]></programlisting></para>
&lt;beans:bean id="decider" class="com.MyDecider"/&gt;</programlisting></para>
</section>
<section id="split-flows">
@@ -1532,16 +1510,95 @@ itemWriter.write(items);]]></programlisting>
elements such as the 'next' attribute or the 'next', 'end', 'fail', or
'pause' elements.</para>
<programlisting><![CDATA[<split id="split1" next="step4">
<flow>
<step id="step1" parent="s1" next="step2"/>
<step id="step2" parent="s2"/>
</flow>
<flow>
<step id="step3" parent="s3"/>
</flow>
</split>
<step id="step4" parent="s4"/>]]></programlisting>
<programlisting>&lt;split id="split1" next="step4"&gt;
&lt;flow&gt;
&lt;step id="step1" parent="s1" next="step2"/&gt;
&lt;step id="step2" parent="s2"/&gt;
&lt;/flow&gt;
&lt;flow&gt;
&lt;step id="step3" parent="s3"/&gt;
&lt;/flow&gt;
&lt;/split&gt;
&lt;step id="step4" parent="s4"/&gt;</programlisting>
</section>
<section id="split-flows">
<title>Externalizing Flow Definitions and Dependencies Between
Jobs</title>
<para>Part of the flow in a job can be externalized as a separate bean
definition, and then re-used. There are three ways to do this, and the
first is to simply declare the flow as a reference to one defined
elsewhere:</para>
<programlisting>&lt;job id="job"&gt;
&lt;flow id="job1.flow1" parent="flow1" next="step3"/&gt;
&lt;step id="step3" parent="s3"/&gt;
&lt;/job&gt;
&lt;flow id="flow1"&gt;
&lt;step id="step1" parent="s1" next="step2"/&gt;
&lt;step id="step2" parent="s2"/&gt;
&lt;/flow&gt;</programlisting>
<para>The effect of defining an external flow like this is simply to
insert the steps from the external flow into the job as if they had been
declared inline. In this way many jobs can refer to the same template
flow and compose such templates into different logical flows. This is
also a good way to separate the integration testing of the individual
flows.</para>
<para>The second form of an externalized flow is to use a
<classname>FlowStep</classname>. A <classname>FlowStep</classname> is an
implementation of the <classname>Step</classname> interface that
delegates processing to a flow defined as above with a
<literal>&lt;flow/&gt;</literal> element in XML. There is also support
for creating a FlowStep in XML directly:</para>
<para><programlisting>&lt;job id="job"&gt;
&lt;step id="job1.flow1" flow="flow1" next="step3"/&gt;
&lt;step id="step3" parent="s3"/&gt;
&lt;/job&gt;
&lt;flow id="flow1"&gt;
&lt;step id="step1" parent="s1" next="step2"/&gt;
&lt;step id="step2" parent="s2"/&gt;
&lt;/flow&gt;</programlisting>The logic of execution of this job is the same
as the previous example, but the data stored in the job repository is
different: the <classname>Step</classname> "job1.flow1" gets its own
entry in the repository. This can be useful for monitoring and reporting
purposes, and moreover it can be used to give more structure to a <link
linkend="partitioning">partitioned step</link>.</para>
<para>The third form of an externalized flow is to use a
<classname>JobStep</classname>. A <classname>JobStep</classname> is
similar to a <classname>FlowStep</classname>, but actually creates and
launches a separate job execution for the steps in the flow specified.
Here is an example:</para>
<para><programlisting>&lt;job id="jobStepJob" restartable="true"&gt;
&lt;step id="jobStepJob.step1" parent="jobStep"/&gt;
&lt;/job&gt;
&lt;job id="<emphasis role="bold">job</emphasis>" restartable="true"&gt;...&lt;/job&gt;
&lt;bean id="jobStep" class="org.springframework.batch.core.step.job.JobStep"&gt;
&lt;property name="jobRepository" ref="jobRepository"/&gt;
&lt;property name="jobLauncher" ref="jobLauncher"/&gt;
&lt;property name="job" ref="<emphasis role="bold">job</emphasis>"/&gt;
&lt;property name="jobParametersExtractor"&gt;
&lt;bean class="org.springframework.batch.core.step.job.DefaultJobParametersExtractor"&gt;
&lt;property name="keys" value="input.file"/&gt;
&lt;/bean&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting></para>
<para>Again this is useful when you want to have more granular options
for monitoring and reporting on jobs and steps. Using
<classname>JobStep</classname> is also often a good answer to the
question: "How do I create dependencies between jobs?". It is a good way
to break up a large system into smaller modules and control the flow of
jobs.</para>
</section>
</section>
@@ -1555,11 +1612,11 @@ itemWriter.write(items);]]></programlisting>
Flat File resources can be configured using standard Spring
constructs:</para>
<programlisting><![CDATA[<bean id="flatFileItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource"
value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" />
</bean>]]></programlisting>
<programlisting>&lt;bean id="flatFileItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader"&gt;
&lt;property name="resource"
value="file://outputs/20070122.testStream.CustomerReportStep.TEMP.txt" /&gt;
&lt;/bean&gt;</programlisting>
<para>The above <classname>Resource</classname> will load the file from
the file system location specified. Note that absolute locations have to
@@ -1569,10 +1626,10 @@ itemWriter.write(items);]]></programlisting>
at runtime as a parameter to the job. This could be solved using '-D'
parameters, i.e. a system property:</para>
<programlisting><![CDATA[<bean id="flatFileItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="${input.file.name}" />
</bean>]]></programlisting>
<programlisting>&lt;bean id="flatFileItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader"&gt;
&lt;property name="resource" value="${input.file.name}" /&gt;
&lt;/bean&gt;</programlisting>
<para>All that would be required for this solution to work would be a
system argument (-Dinput.file.name="file://file.txt"). (Note that although
@@ -1632,17 +1689,17 @@ itemWriter.write(items);]]></programlisting>
must be added explicitly, either by using the <literal>batch</literal>
namespace:</para>
<programlisting><![CDATA[<beans:beans xmlns="http://www.springframework.org/schema/beans"
<programlisting>&lt;beans:beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="...">
xsi:schemaLocation="..."&gt;
...
</beans:beans>]]></programlisting>
&lt;/beans:beans&gt;</programlisting>
<para>or by including a bean definition explicitly for
the<classname>Step</classname> (but not both):</para>
<programlisting><![CDATA[<bean class="org.springframework.batch.core.scope.StepScope" />]]></programlisting>
<programlisting>&lt;bean class="org.springframework.batch.core.scope.StepScope" /&gt;</programlisting>
</section>
</section>
</chapter>
</chapter>