Stop task when there is an exception thrown in ItemProcessor - spring-batch

I am designing a Spring Batch, which reads multiple csv files. I have used partitioning to read each file in chunk and process it to decrypt a certain column in the csv. Before decrypting if i encounter any validation error , i throw custom exception.
Now what i want is if the processing finds any validation error in the first line, the other lines should not be processed, and the job should end.
How can i achieve this? I tried to implement ProcessorListener too but it has no StepExecution object so that i can call SetTerminateOnly() or ExitStatus=Failed
Also note that i have multiple thread accessing the file in different lines.I want to kill all threads in the event of the first encountered error.
Thanks in advance

So, I identified that running multiple asynchronous concurrent threads (Spring Batch partitioning) was the real issue. Though one of the thread threw an Exception, the other threads were parallely running, and finished executing till the end.
Ath the end, the Job FAILED overall and there was no output processed, but it consumed time to process rest of the data.
Well,the solution to it is as simple as it gets. We just need stop the Job while encountering an error during processing.
The Custom Processor
public class MultiThreadedFlatFileItemProcessor implements ItemProcessor<BinFileVO, BinFileVO>,JobExecutionListener{
private JobExecution jobExecution;
private RSADecrypter decrypter;
public RSADecrypter getDecrypter() {
return decrypter;
}
public void setDecrypter(RSADecrypter decrypter) {
this.decrypter = decrypter;
}
#Override
/**
This method is used process the encrypted data
#param item
* */
public BinFileVO process(BinFileVO item) throws JobException {
if(null!=item.getEncryptedText() && !item.getEncryptedText().isEmpty()){
String decrypted = decrypter.getDecryptedText(item.getEncryptedText());
if(null!=decrypted && !decrypted.isEmpty()){
if(decrypted.matches("[0-9]+")){
if(decrypted.length() >= 12 && decrypted.length() <= 19){
item.setEncryptedText(decrypted);
}else{
this.jobExecution.stop();
throw new JobException(PropertyLoader.getValue(ApplicationConstants.DECRYPTED_CARD_NO_LENGTH_INVALID),item.getLineNumber());
}
}
}else{
this.jobExecution.stop();
throw new JobException(PropertyLoader.getValue(ApplicationConstants.EMPTY_ENCRYPTED_DATA),item.getLineNumber());
}
return item;
}
#Override
public void beforeJob(JobExecution jobExecution) {
this.jobExecution=jobExecution;
}
#Override
public void afterJob(JobExecution jobExecution) {
}
}
The Job xml config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
.....>
<!-- JobRepository and JobLauncher are configuration/setup classes -->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" />
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<!-- Job Details -->
<job id="simpleMultiThreadsReaderJob" xmlns="http://www.springframework.org/schema/batch">
<step id="step" >
<partition step="step1" partitioner="partitioner">
<handler grid-size="5" task-executor="taskExecutor"/>
</partition>
</step>
<listeners>
<listener ref="decryptingItemProcessor"/>
</listeners>
</job>
<step id="step1" xmlns="http://www.springframework.org/schema/batch">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" processor="decryptingItemProcessor" commit-interval="500"/>
<listeners>
<listener ref="customItemProcessorListener" />
</listeners>
</tasklet>
</step>
<!-- Processor Details -->
<bean id="decryptingItemProcessor" class="com.test.batch.io.MultiThreadedFlatFileItemProcessor">
<property name="decrypter" ref="rsaDecrypter" />
</bean>
<!-- RSA Decrypter class -->
<bean id="rsaDecrypter" class="test.batch.secure.rsa.client.RSADecrypter"/>
<!-- Partitioner Details -->
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="partitioner" class="com.test.batch.partition.FlatFilePartitioner" scope="step">
<property name="resource" ref="inputFile"/>
</bean>
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10"/>
</bean>
<!-- Step will need a transaction manager -->
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
........
.................
</beans>
Here are the logs
2016-09-01 06:32:40 INFO SimpleJobRepository:273 - Parent JobExecution is stopped, so passing message on to StepExecution
2016-09-01 06:32:43 INFO ThreadStepInterruptionPolicy:60 - Step interrupted through StepExecution
2016-09-01 06:32:43 INFO AbstractStep:216 - Encountered interruption executing step: Job interrupted status detected.
; org.springframework.batch.core.JobInterruptedException
2016-09-01 06:32:45 ERROR CustomJobListener:163 - exception :At line No. 1 : The decrypted card number is less than 12 or greater than 19 in length
2016-09-01 06:32:45 ERROR CustomJobListener:163 - exception :Job interrupted status detected.
2016-09-01 06:32:45 INFO SimpleJobLauncher:135 - Job: [FlowJob: [name=simpleMultiThreadsReaderJob]] completed with the following parameters: [{outputFile=/usr/local/pos/bulktokenization/csv/outputs/cc_output_EDWError_08162016.csv, partitionFile=/usr/local/pos/bulktokenization/csv/partitions/, inputFile=C:\usr\local\pos\bulktokenization\csv\inputs\cc_input_EDWError_08162016.csv, fileName=cc_input_EDWError_08162016}] and the following status: [FAILED]
2016-09-01 06:32:45 INFO BatchLauncher:122 - Exit Status : FAILED
2016-09-01 06:32:45 INFO BatchLauncher:123 - Time Taken : 8969

If we throw Custom Exception in Processor, Spring Batch will terminate and mark the job failed unless you setup 'skipable' exception. You have not mentioned where you perform validate step, are you doing in Processor or Reader? Let me know because it is where Spring Batch decides.
In my project, if I want to stop the job and throw Custom Exception, we put validation logic in a Tasklet or Processor and throw exception as below
private AccountInfoEntity getAccountInfo(Long partnerId) {
if(partnerId != null){
.....
return ....;
} else {
throw new ReportsException("XXXXX");
}
}

Related

Spring Batch: DAO call from processor slows down batch process -- can I reuse DB connection?

When I add a DAO call in the MdwValidatingItemProcessor below, I get a serious hit in performance. Prior to adding the additional DAO call, 2500 records where being read/processed/written in 40ish seconds. When I add the one additional DAO call (lobPolicyMapper.getPolicyOrigin(item.getLob(),item.getRgn_cd())
), it becomes 120 seconds. And the lob_policy table only has one record in it. I plan to have many such DAO calls in my processor to do additional validation of the item. Can I reuse the DAO's connection so for each processor that is validating an item, the connection doesn't have to be opened and closed constantly? The AsyncItemProcessor delegates to the MdwValidatingItemProcessor:
public class MdwValidatingItemProcessor implements
ItemProcessor<MecMdw, MecMdw> {
#Autowired
DataSourceTransactionManager txManager;
#Autowired
LobPolicyMapper lobPolicyMapper;
#Autowired
MecUtils mecUtils;
#Autowired
LobShopMapper lobShopMapper;
#Autowired
KaiserIssuerMapper kaiserIssuerMapper;
private Validator validator;
public void setValidator(Validator validator) {
this.validator = validator;
}
private List<Map> validationReasons;
#BeforeStep
public void beforeStep(StepExecution stepExecution) {
validationReasons = (List<Map>) stepExecution.getJobExecution().getExecutionContext().get("validationReasons");
}
public MecMdw process(MecMdw item) throws Exception {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
BindingResult results = BindAndValidate(item);
try {
if (results.hasErrors()) {
buildValidationException(results, item);
return setAsKickOut(item);
}
mecUtils.checkForPaddingAndValidateSSN(item);
//if both SSN and DOB are valid, set Rep_DOB_or_SSN to be SSN
if (item.getInvalid_ssn().equals(MecConstants.Y_FLAG) && mecUtils.isDOBValid(item)) {
item.setRep_doborssn("SSN");
}
else if(mecUtils.isDOBValid(item)){
item.setRep_doborssn("DOB");
}
else{
List<String> listOfErrors = new ArrayList<String>();
listOfErrors.add("BothSSNAndDOBInvalid");
item.setValidationErrors(listOfErrors);
return setAsKickOut(item);
}
// Policy Origin code not found in MEC database based on Region /
// LOB.
LobPolicy lobPolicy = lobPolicyMapper.getPolicyOrigin(
item.getLob(), item.getRgn_cd());
if (lobPolicy == null || ("").equals(lobPolicy.getLob())
|| lobPolicy.getLob() == null) {
return setAsKickOut(item);
}
//set the Rep_PolicyOgnCd
item.setRep_policy_ogn_cd(lobPolicy.getPolicyOrigin());
//If origin of policy = SHOP, look for Shop Identifier.(mec_lob_shop table)
if(("SHOP").equals(lobPolicy.getPolicyOrigin())){
if(lobShopMapper.getValidationShopIdentifier(item) == null){
return setAsKickOut(item);
}
}
//Validation Shop Identifier not found in MEC database based on Region/LOB/PID
if(lobShopMapper.getValidationShopIdentifier(item) == null){
return setAsKickOut(item);
}
//Kaiser Issuer EIN not found in MEC database based on Region/LOB.
Integer kaiserIssuer = kaiserIssuerMapper.getKaiserIssuerIdWithLobAndRegion(item);
if(kaiserIssuer == 0){
return setAsKickOut(item);
}
else{
item.setRep_kaiser_issuer_id(kaiserIssuer.toString() );
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
txManager.commit(status);
}
return item;
}
private MecMdw setAsKickOut(MecMdw item) {
item.setKick_out_fl('Y');
return item;
}
private BindingResult BindAndValidate(MecMdw item) {
DataBinder binder = new DataBinder(item);
binder.setValidator(validator);
binder.validate();
return binder.getBindingResult();
}
private void buildValidationException(BindingResult results, MecMdw item) {
List<String> listOfErrors = new ArrayList<String>();
for (ObjectError error : results.getAllErrors()) {
String[] codes = error.getCodes();
listOfErrors.add(codes[1]);
}
item.setValidationErrors(listOfErrors);
// throw new ValidationException(msg.toString());
}
I have a batch job using AsyncItemProcessor and AsyncItemWriter as follows:
<job id="mecmdwvalidatorJob" xmlns="http://www.springframework.org/schema/batch">
<step id="mdwvalidatorStep1">
<tasklet>
<chunk reader="pageItemReader" processor="asyncItemProcessor"
writer="asynchItemWriter" commit-interval="1000" skip-limit="2147483647">
<skippable-exception-classes> <!-- TODO -->
<include class="java.lang.Exception" />
</skippable-exception-classes>
</chunk>
</tasklet>
</step>
</job>
<bean id="pageItemReader"
class="org.springframework.batch.item.database.JdbcPagingItemReader">
<property name="dataSource" ref="dataSource" />
<property name="queryProvider">
<bean
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="selectClause"
value="select MDW_ID,FK_LOG_FILE_ID,TAX_YEAR,SUBS_TYPE_CD,SUB_FIRST_NM,SUB_MIDDLE_NM,SUB_LAST_NM,SUB_SUFFIX,SUB_DOB,SUB_ADDR1,SUB_ADDR2,SUB_CITY,SUB_STATE,SUB_PROVINCE,SUB_ZIP,SUB_ZIP4,SUB_COUNTRY_CD,SUB_COUNTRY,SUB_F_POSTAL_CD,LOB,SUB_SSN,GRP_EMP_NAME1,GRP_EMP_NAME2,GRP_EIN,GRP_ADDR1,GRP_ADDR2,GRP_CITY,GRP_STATE,GRP_PROVINCE,GRP_ZIP,GRP_ZIP4,GRP_COUNTRY_CD,GRP_COUNTRY,GRP_F_POSTAL_CD,ISSUER_NAME1,ISSUER_NAME2,ISSUER_PHONE,ISSUER_ADDR1,ISSUER_ADDR2,ISSUER_CITY,ISSUER_PROVINCE,ISSUER_ZIP,ISSUER_ZIP4,ISSUER_COUNTRY_CD,ISSUER_COUNTRY,ISSUER_F_POSTAL_CD,MEM_FIRST_NM,MEM_MIDDLE_NM,MEM_LAST_NM,MEM_SUFFIX,MEM_SSN,MEM_DOB,MEM_START_DATE,MEM_END_DATE,REGION_CD,SUB_MRN,SUB_MRN_PREFIX,MEM_MRN,MRN_PREFIX,PID,SUB_GRP_ID,SUB_GRP_NAME,INVALID_ADDR_FL" />
<property name="fromClause"
value="from MEC_MDW JOIN MEC_FILE_LOG on MEC_FILE_LOG.LOG_FILE_ID=MEC_MDW.FK_LOG_FILE_ID " />
<property name="whereClause" value="where MEC_FILE_LOG.STATUS=:status" />
<property name="sortKey" value="MDW_ID" />
</bean>
</property>
<property name="parameterValues">
<map>
<entry key="status" value="READY TO VALIDATE" />
</map>
</property>
<property name="pageSize" value="1000" />
<property name="rowMapper" ref="mdwRowMapper" />
</bean>
<bean id="mdwRowMapper" class="org.my.rowmapper.MdwRowMapper" />
<bean id="asyncItemProcessor"
class="org.springframework.batch.integration.async.AsyncItemProcessor">
<property name="delegate">
<bean
class="org.my.itemprocessor.MdwValidatingItemProcessor">
<property name="validator">
<bean
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
</property>
</bean>
</property>
<property name="taskExecutor" ref="taskExecutor" />
</bean>
<task:executor id="taskExecutor" pool-size="10" />
<bean id="asynchItemWriter"
class="org.springframework.batch.integration.async.AsyncItemWriter">
<property name="delegate" ref="customerCompositeWriter">
</property>
</bean>
<bean id="customerCompositeWriter"
class="org.springframework.batch.item.support.CompositeItemWriter">
<property name="delegates">
<list>
<ref bean="itemWriter1" />
<ref bean="itemWriter2" />
</list>
</property>
</bean>
<bean id="itemWriter1" class="org.my.writer.MdwWriter" />
<bean id="itemWriter2" class="org.my.writer.KickoutWriter" />
The transaction manager and MyBatix SqlSessionTemplate:
<!-- transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- define SqlSessionFactory as BATCH execution -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
<constructor-arg index="1" value="BATCH" />
</bean>
<!-- stored job-meta in database -->
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="batchTransactionManager" />
<property name="databaseType" value="sqlserver" />
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
The DAO making the call:
public interface LobPolicyMapper {
public abstract LobPolicy getPolicyOrigin(#Param("lob") String lob, #Param("regionCd") String regionCd);
UPDATE I added ehcache to the the MyBatis XML. This will help with the repetitive queries. But again, I am looking for a way to share this same DAO's connection across the AsyncItemProcessors:
<mapper namespace="org.mybatis.LobPolicyMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<select id="getPolicyOrigin" parameterType="hashmap" resultType='org.my.domain.LobPolicy'>
SELECT
l.lob_id as lobId,
l.lob as lob,
l.policy_origin as policyOrigin,
l.region_cd as regionCd
from mec_lob_policy l
where lob= #{lob} and region_cd=#{regionCd}
</select>
I added ehcache to the project
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.0</version>
</dependency>
I think I need a database pool. Here is what I am seeing in my logs on exiting the processor:
2015-08-27 17:09:54,194 [taskExecutor-3] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#77b027f]
2015-08-27 17:10:06,674 [taskExecutor-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#77b027f]
2015-08-27 17:10:06,675 [taskExecutor-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#77b027f]
2015-08-27 17:10:06,675 [taskExecutor-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#77b027f]
2015-08-27 17:10:06,675 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
2015-08-27 17:10:06,675 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [ConnectionID:14 ClientConnectionId: 1b95dc0e-83c0-487b-af59-f5be52931818]
2015-08-27 17:10:06,805 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [ConnectionID:14 ClientConnectionId: 1b95dc0e-83c0-487b-af59-f5be52931818] after transaction
2015-08-27 17:10:06,805 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
Here is what I am seeing in my logs on entering the processor. New transaction is created and a new database connection is fetched:
2015-08-27 17:10:06,805 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2015-08-27 17:10:06,805 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:sqlserver://blah]
2015-08-27 17:10:07,115 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [ConnectionID:23 ClientConnectionId: d1f016e6-3e9d-4b0e-a34d-14298c292a65] for JDBC transaction
2015-08-27 17:10:07,115 [taskExecutor-3] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [ConnectionID:23 ClientConnectionId: d1f016e6-3e9d-4b0e-a34d-14298c292a65] to manual commit

FlowExecutionException when a caught exception occurs in ItemWriter implementing StepListener

I am having an exception caused in Spring Batch code that I suspect is due to some bad configuration. First I will give context and then the problem I am having.
I am using Spring Batch 2.2.6.RELEASE
I have a job defined like this (simplified excerpts that I consider are the relevant ones):
....
<batch:job id="job1">
<batch:step id="step1">
<batch:tasklet ref="myTasklet1"/>
</batch:step>
<batch:step id="step2" >
<batch:tasklet ref="myTasklet2"/>
</batch:step>
<batch:step id="step3">
<batch:tasklet>
<batch:chunk reader="myReader" processor="myProcessor" writer="myCompositeWriter" commit-interval="10" />
</batch:tasklet>
<batch:listeners>
<batch:listener ref="myWriter2" />
</batch:listeners>
</batch:step>
<batch:step id="step4" >
<batch:tasklet ref="myTasklet4"/>
</batch:step>
</batch:job>
...
<bean id="myCompositeWriter " class="org.springframework.batch.item.support.CompositeItemWriter">
<property name="delegates">
<list>
<ref bean="myWriter1" />
<ref bean="myWriter2" />
</list>
</property>
</bean>
<bean id="myWriter2" class="my.test.MyWriter2" scope="step" />
...
The simplified writer2 as follows:
public class MyWriter2 implements ItemWriter<Object>, StepExecutionListener {
private ExecutionContext jobContext;
#Override
public void beforeStep(StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
this.jobContext = jobExecution.getExecutionContext();
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
return null;
}
#Override
public void write(List<? extends Object> items) {
try {
// database insertion
} catch (Exception e) {
// add exception to context for later notifications
jobContext.put("writer2_error", e);
}
}
}
Some requirements:
Need to access the jobContext from all tasklets and from writer2. Accessing the jobcontext from tasklets is straightforward. The writer2 implements StepExecutionListener and it is registered as a listener in step3 to be able to access it.
The writer2 inserts data into a database. This operation may fail but should allow the job to continue the execution and if everything else works fine then the job should end successfully. That is the reason why the insertion exceptions are all caught.
The problem:
If the writer operation in writer2 fails the exception is caught but the job fails after step3.
In Spring Batch Admin console the steps 1, 2 and 3 statuses are COMPLETED, the step 4 status is NONE, the job status and exit code is FAILED and the next exception is shown:
org.springframework.batch.core.JobExecutionException: Flow execution ended unexpectedly at
org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:141) at
org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:301) at
org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:134) at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) Caused by:
org.springframework.batch.core.job.flow.FlowExecutionException: Ended flow=job1 at state=step3 with exception at
org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:160) at
org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:130) at
org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:135)
... 5 more Caused by: java.util.EmptyStackException at
org.codehaus.jettison.util.FastStack.peek(FastStack.java:39) at
org.codehaus.jettison.mapped.MappedXMLStreamWriter.setNewValue(MappedXMLStreamWriter.java:121) at
org.codehaus.jettison.mapped.MappedXMLStreamWriter.makeCurrentJSONObject(MappedXMLStreamWriter.java:113) at
org.codehaus.jettison.mapped.MappedXMLStreamWriter.writeStartElement(MappedXMLStreamWriter.java:241) at
com.thoughtworks.xstream.io.xml.StaxWriter.startNode(StaxWriter.java:162) at
com.thoughtworks.xstream.io.xml.AbstractXmlWriter.startNode(AbstractXmlWriter.java:37) at
com.thoughtworks.xstream.io.WriterWrapper.startNode(WriterWrapper.java:33) at
com.thoughtworks.xstream.io.path.PathTrackingWriter.startNode(PathTrackingWriter.java:44) at
com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper.startNode(ExtendedHierarchicalStreamWriterHelper.java:17) at
com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.writeItem(AbstractCollectionConverter.java:62) at
com.thoughtworks.xstream.converters.collections.MapConverter.marshal(MapConverter.java:57) at
com.thoughtworks.xstream.core.AbstractReferenceMarshaller.convert(AbstractReferenceMarshaller.java:65) at
com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:78) at
com.thoughtworks.xstream.core.TreeMarshaller.convertAnother(TreeMarshaller.java:63) at com.thoughtworks.xstream.core.Tree
If no exceptions occur then the job ends successfully.
Any ideas why this could be failing?
Thanks.

Couldn't receive values in destination step using ExecutionContextPromotionListener

I want to pass some information from one step to another step in a batch job. Created BStepListener where the value into the context is stored, but this same value is not coming to tasklet [SendMailTasklet] created in another step. Where am i missing?
Job configuration
<job id="bJob" xmlns="http://www.springframework.org/schema/batch">
<step id="step1">
<tasklet>
<chunk reader="bReader" writer="bWriter" processor="bProcessor"
commit-interval="10" />
</tasklet>
<batch:next on="COMPLETED" to="sendEmail"/>
<listeners>
<listener ref="bStepListner"/>
<listener ref="bPromotionListener"/>
</listeners>
</step>
<step id="sendEmail">
<tasklet ref="sendMailManager"/>
</step>
</job>
<bean id="bStepListner" class="com.listener.BStepListener" scope="step"/>
<bean id="bPromotionListener" class="org...ExecutionContextPromotionListener">
<property name="keys" value="msg"/>
</bean>
<bean id="sendMailManager" class="com.mail.SendMailTasklet" scope="step">
BStepListener.java
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("Step Execution Listener ... after Step");
String message = "A Sample message from step to step";
stepExecution.getExecutionContext().put("msg", message);
return null;
}
SendMailTasklet.java
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext)
throws Exception {
logger.info("Sending Email service....");
String message = (String)chunkContext.getStepContext().getJobExecutionContext().get("msg");
this.sendMail();
return RepeatStatus.FINISHED;
}
I think (I'd have to double check the code) that we don't guarantee the order listeners are called. Because of that, the promotion listener may be being called before yours is. Try using the CompositeStepExecutionListener to wrap your list of listeners so that order is preserved.
You can read more about the CompositeStepExecutionListener here: http://docs.spring.io/spring-batch/trunk/apidocs/org/springframework/batch/core/listener/CompositeStepExecutionListener.html

Reading Records From a Database in Spring Batch

I'm trying to read some records from a database using loops then do some calculations on the records (updating a field called total).
But i'm new to spring batch so please can anyone provide me with some tips.
this sounds like something the chunk pattern would address. you can use re-use existing Spring Batch components to read from the database, compose your own processor, then pass back to a Spring Batch component to store.
say the use case is like this;
- read a record
- record.setTotalColumn(record.getColumn2() + record.getColumn3())
- update
this configuration might look like this
<batch:job id="recordProcessor">
<batch:step id="recordProcessor.step1">
<batch:tasklet>
<batch:chunk reader="jdbcReader" processor="calculator" writer="jdbcWriter" commit-interval="10"/>
</batch:tasklet>
</batch:step>
</batch:job>
<bean id="jdbcReader" class="org.springframework.batch.item.database.JdbcCursorItemReader">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="select idColumn,column1,column2,totalColumn from my_table"/>
<property name="rowMapper" ref="myRowMapper"/>
</bean>
<bean id="jdbcWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value="update my_table set totalColumn = :value where idColumn = :id"/>
</bean>
<bean id="calculator" class="de.incompleteco.spring.batch.step.item.CalculationProcessor"/>
this means that the only thing you have to 'write' from scratch would be the calculator processor which could e something like this;
package de.incompleteco.spring.batch.step.item;
import org.springframework.batch.item.ItemProcessor;
public class CalculationProcessor implements ItemProcessor<MyObject, MyObject> {
#Override
public MyObject process(MyObject item) throws Exception {
//do the math
item.setTotalColumn(item.getColumn1() + item.getColumn2());
//return
return item;
}
}

problems with ApplicationContext and Spring batch

i'm working with Spring batch, i've done the batch job, configured with an xml file,
i also put all the Quartz configuration in that xml file, (the trigger, schedulerFactoryBean and jobDetail); this is a java project, and i'm trying to load the application context, as an stand alone in a main class; as far as the documentation says, this should make Quartz to start running and is doing it, the problem is when the job runs with the trigger and calls the service, is like all the Autowired beans hadn’t had been loaded, so is giving me an NullpointerException…
this is the code that the job calls after the trigger is fired, and when the JobParametersBuilder is created is when everything crash, Quartz still running though...
could someone helpme with this?
//class called by the job
public class MainJobClass {
private static Logger log = Logger.getLogger(MainJobClass.class);
#Autowired
private SimpleJobLauncher launcher;
#Autowired
private Job job;
public void executeJob(){
try{
log.info("***** Staring job......");
JobParametersBuilder builder = new JobParametersBuilder();
builder.addDate("date", new Date());
builder.addString("sendEmailJob", "Send email to approvers");
JobParameters parameters = builder.toJobParameters();
launcher.run(job, parameters);
}catch(Exception e){
log.error("Error on executing job"+e.fillInStackTrace());
}
}
public void setLauncher(SimpleJobLauncher launcher) {
this.launcher = launcher;
}
public void setJob(Job job) {
this.job = job;
}
simple main method calling App context:
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("/com/ge/grt/email/grt_email_send.xml");
}
error line:
INFO [DefaultQuartzScheduler_Worker-1] (MainJobClass.java:29) - ***** Staring job......
ERROR [DefaultQuartzScheduler_Worker-1] (MainJobClass.java:40) - Error on executing jobjava.lang.NullPointerException
this are the Quartz beans on the xml file:
<!-- Scheudler Factory bean, the job will run when the context is loaded -->
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="beanTrigger"></ref>
</list>
</property>
</bean>
<!-- definition of the trigger -->
<!-- defining the execution date: (once every week on monday at 8:00 AM) -->
<bean id="beanTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="misfireInstructionName" value="MISFIRE_INSTRUCTION_FIRE_ONCE_NOW"/>
<!-- <property name="cronExpression" value="0 0 8 ? * MON" /> -->
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>
<!-- definiton of job detail bean -->
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="mainJobClass" />
<property name="targetMethod" value="executeJob" />
<property name="concurrent" value="false"></property>
</bean>
Try org.springframework.scheduling.quartz.JobDetailBean along with jobDataAsMap for job class DI
Ex:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/scheduling.html#scheduling-quartz-jobdetail