1
votes

This is my first post in here. I apologies upfront if I am not adhreing to group rules.

Basically I am stuck in a problem related to Spring Batch.

I need to read and process a file with multiple records. Something like below:

HEADER
BATCH
CUST
TRANS
CUST
OPT
TRANS
CUST
OPT
TRANS
CUST
OPT
TRANS
CUST
OPT
TRANS
BATCHFOOTER
CUSTFOOTER
TRANFOOTER

I just need to read lines start with CUST AND TRANS and skip all others. I have tried with PatternMatchingCompositeLineMapper, but if we dont map others then we get exceptions. How can I skip certain rows? Help. Thanks in advance.

Regards, Taran

3

3 Answers

2
votes

Spring batch have the capability of reading complex files. only thing is we have to write our own readers to process complex files.any file having particular pattern we can read it through spring Batch.

this is file format like your file

CUST,Warren,Q,Darrow,8272 4th Street,New York,IL,76091
TRANS,1165965,2011-01-22 00:13:29,51.43
CUST,Ann,V,Gates,9247 Infinite Loop Drive,Hollywood,NE,37612
CUST,Erica,I,Jobs,8875 Farnam Street,Aurora,IL,36314
TRANS,8116369,2011-01-21 20:40:52,-14.83
TRANS,8116369,2011-01-21 15:50:17,-45.45
TRANS,8116369,2011-01-21 16:52:46,-74.6
TRANS,8116369,2011-01-22 13:51:05,48.55
TRANS,8116369,2011-01-21 16:51:59,98.53

Custom FileReader

    import Java.util.ArrayList;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemStreamReader;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
public class CustomerFileReader implements ItemStreamReader<Object> {
private Object curItem = null;
private ItemStreamReader<Object> delegate;
public Object read() throws Exception {
if(curItem == null) {
curItem = (Customer) delegate.read();
}
Customer item = (Customer) curItem;
curItem = null;
if(item != null) {
item.setTransactions(new ArrayList<Transaction>());
while(peek() instanceof Transaction) {
curItem = null;
}
}
return item;
}
public Object peek() throws Exception, UnexpectedInputException,
ParseException {
if (curItem == null) {
curItem = delegate.read();
}
return curItem;
}
public void setDelegate(ItemStreamReader<Object> delegate) {
this.delegate = delegate;
}
public void close() throws ItemStreamException {
delegate.close();
}
public void open(ExecutionContext arg0) throws ItemStreamException {
delegate.open(arg0);
}
public void update(ExecutionContext arg0) throws ItemStreamException {
delegate.update(arg0);
}
}

Configuration

    <beans:bean id="customerFile"
class="org.springframework.core.io.FileSystemResyource" scope="step">
<beans:constructor-arg value="#{jobParameters[customerFile]}"/>
</beans:bean>
<beans:bean id="customerFileReader"
class="com.apress.springbatch.chapter7.CustomerFileReader">
<beans:property name="delegate" ref="trueCustomerFileReader"/>
</beans:bean>
<beans:bean id="trueCustomerFileReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<beans:property name="resource" ref="customerFile" />
<beans:property name="lineMapper">
<beans:bean class="org.springframework.batch.item.file.mapping.
PatternMatchingCompositeLineMapper">
<beans:property name="tokenizers">
<beans:map>
<beans:entry key="CUST*" value-ref="customerLineTokenizer"/>
<beans:entry key="TRANS*" value-ref="transactionLineTokenizer"/>
</beans:map>
</beans:property>
<beans:property name="fieldSetMappers">
<beans:map>
<beans:entry key="CUST*" value-ref="customerFieldSetMapper"/>
<beans:entry key="TRANS*" value-ref="transactionFieldSetMapper"/>
</beans:map>
</beans:property>
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="customerLineTokenizer"
class="org.springframework.batch.item.file.transform.
DelimitedLineTokenizer">
<beans:property name="names" value="prefix,firstName,middleInitial,
lastName,address,city,state,zip"/>
<beans:property name="delimiter" value=","/>
</beans:bean>
<beans:bean id="transactionLineTokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<beans:property name="names"
value="prefix,accountNumber,transactionDate,amount"/>
<beans:property name="delimiter" value=","/>
</beans:bean>
<beans:bean id="customerFieldSetMapper"
class="org.springframework.batch.item.file.mapping.
BeanWrapperFieldSetMapper">
<beans:property name="prototypeBeanName" value="customer"/>
</beans:bean>
<beans:bean id="transactionFieldSetMapper"
class="com.apress.springbatch.chapter7.TransactionFieldSetMapper"/>
<beans:bean id="customer" class="com.apress.springbatch.chapter7.Customer"
scope="prototype"/>

Outpur Writer

<beans:bean id="outputFile"
class="org.springframework.core.io.FileSystemResyource" scope="step">
<beans:constructor-arg value="#{jobParameters[outputFile]}"/>
</beans:bean>
<beans:bean id="outputWriter"
class="org.springframework.batch.item.file.FlatFileItemWriter">
<beans:property name="resource" ref="outputFile" />
<beans:property name="lineAggregator">
<beans:bean class="org.springframework.batch.item.file.transform.
PassThroughLineAggregator"/>
</beans:property>
</beans:bean>

output will be

Warren Q. Darrow has 1 transactions.
Ann V. Gates has no transactions.
Erica I. Jobs has 5 transactions.

CustomerFieldSetMapper

import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class CustomerFieldSetMapper implements FieldSetMapper<Customer> {
public Customer mapFieldSet(FieldSet fieldSet) throws BindException {
Customer customer = new Customer();
customer.setAddress(fieldSet.readString("addressNumber") +
" " + fieldSet.readString("street"));
customer.setCity(fieldSet.readString("city"));
customer.setFirstName(fieldSet.readString("firstName"));
customer.setLastName(fieldSet.readString("lastName"));
customer.setMiddleInitial(fieldSet.readString("middleInitial"));
customer.setState(fieldSet.readString("state"));
customer.setZip(fieldSet.readString("zip"));
return customer;
}
}
2
votes

Use PatternMatchingCompositeLineMapper to tokenize line and mapping to correct domain bean for CUST and TRANS line.
For lines excepts CUST and TRANS write a generic FieldSetMapper used to map from a line to a generic bean (we call it GenericBeanToSkip.).
Using a ClassifierCompositeItemProcessor process CUST and TRANS transformed beans, but skip GenericBeanToSkip returning null from ItemProcessor.process().

0
votes

You can probably use FlatFileItemReader to read each line (you posted only one column but I guess you have more info in rows so use Tokenizer to separate values per row). Than in ItemProcessor check if first token is either CUST or TRANS, if it is return value to writer and if not return null which is spring batch mechanism of filtering items.

You can find explanation on skipping inside spring batch here