RESOLVED - issue BATCH-581: Add filter capability to item oriented paradigm
http://jira.springframework.org/browse/BATCH-581 Added a new sample job: 'CustomerFilterJob' that represents a use case for the filtering of items in a processor.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beansProjectDescription>
|
||||
<version>1</version>
|
||||
<pluginVersion><![CDATA[2.0.6.v200806241357]]></pluginVersion>
|
||||
<pluginVersion><![CDATA[2.1.0.v200808011800]]></pluginVersion>
|
||||
<configSuffixes>
|
||||
<configSuffix><![CDATA[xml]]></configSuffix>
|
||||
</configSuffixes>
|
||||
@@ -42,6 +42,7 @@
|
||||
<config>src/main/resources/org/springframework/batch/sample/config/common-context.xml</config>
|
||||
<config>src/main/resources/jobs/taskletJob.xml</config>
|
||||
<config>src/main/resources/jobs/headerFooterSample.xml</config>
|
||||
<config>src/main/resources/jobs/customerFilterJob.xml</config>
|
||||
</configs>
|
||||
<configSets>
|
||||
<configSet>
|
||||
@@ -357,5 +358,17 @@
|
||||
<config>src/main/resources/simple-job-launcher-context.xml</config>
|
||||
</configs>
|
||||
</configSet>
|
||||
<configSet>
|
||||
<name><![CDATA[customerFilterJob]]></name>
|
||||
<allowBeanDefinitionOverriding>true</allowBeanDefinitionOverriding>
|
||||
<incomplete>false</incomplete>
|
||||
<configs>
|
||||
<config>src/main/resources/jobs/customerFilterJob.xml</config>
|
||||
<config>src/main/resources/simple-job-launcher-context.xml</config>
|
||||
<config>src/main/resources/data-source-context.xml</config>
|
||||
<config>src/main/resources/data-source-context-init.xml</config>
|
||||
<config>src/main/resources/org/springframework/batch/sample/config/common-context.xml</config>
|
||||
</configs>
|
||||
</configSet>
|
||||
</configSets>
|
||||
</beansProjectDescription>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import org.springframework.batch.item.file.mapping.FieldSet;
|
||||
import org.springframework.batch.item.file.transform.LineTokenizer;
|
||||
|
||||
/**
|
||||
* Composite {@link LineTokenizer} that delegates the tokenization of a line to one of two potential
|
||||
* tokenizers. The file format in this case uses one character, either F, A, U, or D to indicate
|
||||
* whether or not the line is an a footer record, or a customer add, update, or delete, and
|
||||
* will delegate accordingly.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CompositeCustomerUpdateLineTokenizer implements LineTokenizer {
|
||||
|
||||
private LineTokenizer customerTokenizer;
|
||||
private LineTokenizer footerTokenizer;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.item.file.transform.LineTokenizer#tokenize(java.lang.String)
|
||||
*/
|
||||
public FieldSet tokenize(String line) {
|
||||
|
||||
if(line.charAt(0) == 'F'){
|
||||
//line starts with F, so the footer tokenizer should tokenize it.
|
||||
return footerTokenizer.tokenize(line);
|
||||
}
|
||||
else if(line.charAt(0) == 'A' || line.charAt(0) == 'U' || line.charAt(0) == 'D'){
|
||||
//line starts with A,U, or D, so it must be a customer operation.
|
||||
return customerTokenizer.tokenize(line);
|
||||
}
|
||||
else{
|
||||
//If the line doesn't start with any of the characters above, it must obviously be invalid.
|
||||
throw new IllegalArgumentException("Invalid line encountered for tokenizing: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link LineTokenizer} that will be used to tokenize any lines that begin with
|
||||
* A, U, or D, and are thus a customer operation.
|
||||
*
|
||||
* @param customerTokenizer
|
||||
*/
|
||||
public void setCustomerTokenizer(LineTokenizer customerTokenizer) {
|
||||
this.customerTokenizer = customerTokenizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link LineTokenizer} that will be used to tokenize any lines that being with
|
||||
* F and is thus a footer record.
|
||||
*
|
||||
* @param footerTokenizer
|
||||
*/
|
||||
public void setFooterTokenizer(LineTokenizer footerTokenizer) {
|
||||
this.footerTokenizer = footerTokenizer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface CustomerDao {
|
||||
|
||||
CustomerCredit getCustomerByName(String name);
|
||||
|
||||
void insertCustomer(String name, BigDecimal credit);
|
||||
|
||||
void updateCustomer(String name, BigDecimal credit);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Enum representing on of 3 possible actions on a customer update:
|
||||
* Add, update, or delete
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public enum CustomerOperation {
|
||||
ADD('A'), UPDATE('U'), DELETE('D');
|
||||
|
||||
private final char code;
|
||||
private static final Map<Character,CustomerOperation> codeMap;
|
||||
|
||||
private CustomerOperation(char code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
static{
|
||||
codeMap = new HashMap<Character,CustomerOperation>();
|
||||
for(CustomerOperation operation:values()){
|
||||
codeMap.put(operation.getCode(), operation);
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomerOperation fromCode(char code){
|
||||
if(codeMap.containsKey(code)){
|
||||
return codeMap.get(code);
|
||||
}
|
||||
else{
|
||||
throw new IllegalArgumentException("Invalid code: [" + code + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public char getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Immutable Value Object representing an update to the customer as stored in the database.
|
||||
* This object has the customer name, credit ammount, and the operation to be performed
|
||||
* on them. In the case of an add, a new customer will be entered with the appropriate
|
||||
* credit. In the case of an update, the customer's credit is considered an absolute update.
|
||||
* Deletes are currently not supported, but can still be read in from a file.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CustomerUpdate {
|
||||
|
||||
private final CustomerOperation operation;
|
||||
private final String customerName;
|
||||
private final BigDecimal credit;
|
||||
|
||||
public CustomerUpdate(CustomerOperation operation, String customerName, BigDecimal credit) {
|
||||
this.operation = operation;
|
||||
this.customerName = customerName;
|
||||
this.credit = credit;
|
||||
}
|
||||
|
||||
public CustomerOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public BigDecimal getCredit() {
|
||||
return credit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Customer Update, name: [" + customerName + "], operation: [" + operation + "], credit: [" + credit + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
|
||||
import org.springframework.batch.item.file.mapping.FieldSet;
|
||||
import org.springframework.batch.item.file.mapping.FieldSetMapper;
|
||||
|
||||
/**
|
||||
* {@link FieldSetMapper} for mapping the
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CustomerUpdateFieldSetMapper extends StepExecutionListenerSupport implements FieldSetMapper<CustomerUpdate> {
|
||||
|
||||
StepExecution stepExecution;
|
||||
|
||||
public CustomerUpdate mapLine(FieldSet fs) {
|
||||
|
||||
char code = fs.readChar(0);
|
||||
if(code == 'F'){
|
||||
long customerUpdateTotal = stepExecution.getReadCount();
|
||||
long fileUpdateTotal = fs.readLong(1);
|
||||
if(customerUpdateTotal != fileUpdateTotal){
|
||||
throw new IllegalStateException("The total number of customer updates in the file footer does not match the " +
|
||||
"number entered File footer total: [" + fileUpdateTotal + "] Total encountered during processing: [" +
|
||||
customerUpdateTotal + "]");
|
||||
}
|
||||
else{
|
||||
//return null, because the footer indicates an end of processing.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
CustomerOperation operation = CustomerOperation.fromCode(code);
|
||||
String name = fs.readString(1);
|
||||
BigDecimal credit = fs.readBigDecimal(2);
|
||||
|
||||
return new CustomerUpdate(operation, name, credit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
|
||||
this.stepExecution = stepExecution;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import static org.springframework.batch.sample.domain.trade.CustomerOperation.*;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CustomerUpdateProcessor implements ItemProcessor<CustomerUpdate, CustomerUpdate>{
|
||||
|
||||
CustomerDao customerDao;
|
||||
InvalidCustomerLogger invalidCustomerLogger;
|
||||
|
||||
public CustomerUpdate process(CustomerUpdate item) throws Exception {
|
||||
|
||||
if(item.getOperation() == DELETE){
|
||||
//delete is not supported
|
||||
invalidCustomerLogger.log(item);
|
||||
return null;
|
||||
}
|
||||
|
||||
CustomerCredit customerCredit = customerDao.getCustomerByName(item.getCustomerName());
|
||||
|
||||
if(item.getOperation() == ADD && customerCredit == null){
|
||||
return item;
|
||||
}
|
||||
else if(item.getOperation() == ADD && customerCredit != null){
|
||||
//veto processing
|
||||
invalidCustomerLogger.log(item);
|
||||
return null;
|
||||
}
|
||||
|
||||
if(item.getOperation() == UPDATE && customerCredit != null){
|
||||
return item;
|
||||
}
|
||||
else if(item.getOperation() == UPDATE && customerCredit == null){
|
||||
//veto processing
|
||||
invalidCustomerLogger.log(item);
|
||||
return null;
|
||||
}
|
||||
|
||||
//if an item makes it through all these checks it can be assumed to be bad, logged, and skipped
|
||||
invalidCustomerLogger.log(item);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setCustomerDao(CustomerDao customerDao) {
|
||||
this.customerDao = customerDao;
|
||||
}
|
||||
|
||||
public void setInvalidCustomerLogger(
|
||||
InvalidCustomerLogger invalidCustomerLogger) {
|
||||
this.invalidCustomerLogger = invalidCustomerLogger;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
|
||||
/**
|
||||
* @author Lucas Wward
|
||||
*
|
||||
*/
|
||||
public class CustomerUpdateWriter implements ItemWriter<CustomerUpdate> {
|
||||
|
||||
private CustomerDao customerDao;
|
||||
|
||||
public void write(List<? extends CustomerUpdate> items) throws Exception {
|
||||
for(CustomerUpdate customerUpdate : items){
|
||||
if(customerUpdate.getOperation() == CustomerOperation.ADD){
|
||||
customerDao.insertCustomer(customerUpdate.getCustomerName(), customerUpdate.getCredit());
|
||||
}
|
||||
else if(customerUpdate.getOperation() == CustomerOperation.UPDATE){
|
||||
customerDao.updateCustomer(customerUpdate.getCustomerName(), customerUpdate.getCredit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCustomerDao(CustomerDao customerDao) {
|
||||
this.customerDao = customerDao;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
/**
|
||||
* Interface for logging invalid customers. Customers may need to be logged because
|
||||
* they already existed when attempted to be added. Or a non existent customer was
|
||||
* updated.
|
||||
*
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public interface InvalidCustomerLogger {
|
||||
|
||||
void log(CustomerUpdate customerUpdate);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade.internal;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.launch.support.CommandLineJobRunner;
|
||||
import org.springframework.batch.sample.domain.trade.CustomerUpdate;
|
||||
import org.springframework.batch.sample.domain.trade.InvalidCustomerLogger;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CommonsLoggingInvalidCustomerLogger implements
|
||||
InvalidCustomerLogger {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(CommandLineJobRunner.class);
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.sample.domain.trade.InvalidCustomerLogger#log(org.springframework.batch.sample.domain.trade.CustomerUpdate)
|
||||
*/
|
||||
public void log(CustomerUpdate customerUpdate) {
|
||||
logger.error("invalid customer encountered: [ " + customerUpdate + "]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.sample.domain.trade.CustomerCredit;
|
||||
import org.springframework.batch.sample.domain.trade.CustomerDao;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.support.JdbcDaoSupport;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class JdbcCustomerDao extends JdbcDaoSupport implements CustomerDao{
|
||||
|
||||
private static final String GET_CUSTOMER_BY_NAME = "SELECT ID, NAME, CREDIT from CUSTOMER where NAME = ?";
|
||||
private static final String INSERT_CUSTOMER = "INSERT into CUSTOMER(NAME, CREDIT) values(?,?)";
|
||||
private static final String UPDATE_CUSTOMER = "UPDATE CUSTOMER set CREDIT = ? where NAME = ?";
|
||||
|
||||
public CustomerCredit getCustomerByName(String name) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<CustomerCredit> customers = (List<CustomerCredit>) getJdbcTemplate().query(GET_CUSTOMER_BY_NAME, new Object[]{name},
|
||||
|
||||
new RowMapper(){
|
||||
|
||||
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
CustomerCredit customer = new CustomerCredit();
|
||||
customer.setName(rs.getString("NAME"));
|
||||
customer.setId(rs.getInt("ID"));
|
||||
customer.setCredit(rs.getBigDecimal("CREDIT"));
|
||||
return customer;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(customers.size() == 0){
|
||||
return null;
|
||||
}
|
||||
else{
|
||||
return customers.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void insertCustomer(String name, BigDecimal credit) {
|
||||
|
||||
getJdbcTemplate().update(INSERT_CUSTOMER, new Object[]{name, credit});
|
||||
}
|
||||
|
||||
public void updateCustomer(String name, BigDecimal credit) {
|
||||
getJdbcTemplate().update(UPDATE_CUSTOMER, new Object[]{credit, name});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
SCust Credit
|
||||
Acustomer5 00032345
|
||||
Ucustomer3 00100500
|
||||
Acustomer6 00123456
|
||||
Dcustomer1 00000000
|
||||
F0000004
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:lang="http://www.springframework.org/schema/lang"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
|
||||
|
||||
<bean name="customerFilterJob" parent="simpleJob">
|
||||
<property name="steps">
|
||||
<list>
|
||||
<bean name="uploadCustomer" parent="skipLimitStep">
|
||||
<property name="itemReader" ref="customerFileUploadReader" />
|
||||
<property name="itemProcessor">
|
||||
<bean class="org.springframework.batch.sample.domain.trade.CustomerUpdateProcessor" >
|
||||
<property name="customerDao" ref="customerDao" />
|
||||
<property name="invalidCustomerLogger" >
|
||||
<bean class="org.springframework.batch.sample.domain.trade.internal.CommonsLoggingInvalidCustomerLogger" />
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemWriter" >
|
||||
<bean class="org.springframework.batch.sample.domain.trade.CustomerUpdateWriter" >
|
||||
<property name="customerDao" ref="customerDao" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="listeners" ref="customerMapper" />
|
||||
</bean>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean name="customerFileUploadReader" class="org.springframework.batch.item.file.FlatFileItemReader" >
|
||||
<property name="resource" ref="customerFileResource" />
|
||||
<property name="lineTokenizer" ref="customerTokenizer" />
|
||||
<property name="linesToSkip" value="1" />
|
||||
<property name="fieldSetMapper" ref="customerMapper" />
|
||||
</bean>
|
||||
|
||||
<bean name="customerMapper" class="org.springframework.batch.sample.domain.trade.CustomerUpdateFieldSetMapper" />
|
||||
|
||||
<bean name="customerTokenizer" class="org.springframework.batch.sample.domain.trade.CompositeCustomerUpdateLineTokenizer" >
|
||||
<property name="customerTokenizer">
|
||||
<bean class="org.springframework.batch.item.file.transform.FixedLengthTokenizer" >
|
||||
<property name="columns" value="1,2-18,19-26" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="footerTokenizer" >
|
||||
<bean class="org.springframework.batch.item.file.transform.FixedLengthTokenizer">
|
||||
<property name="columns" value="1,2-8" />
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean name="customerDao" class="org.springframework.batch.sample.domain.trade.internal.JdbcCustomerDao" >
|
||||
<property name="dataSource" ref="dataSource" />
|
||||
</bean>
|
||||
|
||||
<bean name="customerFileResource" class="org.springframework.core.io.ClassPathResource" >
|
||||
<constructor-arg value="data/customerFilterJob/input/customers.txt" />
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.jdbc.core.RowCallbackHandler;
|
||||
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration()
|
||||
public class CustomerFilterJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
|
||||
|
||||
private static final String GET_CUSTOMERS = "select NAME, CREDIT from CUSTOMER order by NAME";
|
||||
|
||||
private List<Customer> customers;
|
||||
private int activeRow = 0;
|
||||
|
||||
private SimpleJdbcTemplate simpleJdbcTemplate;
|
||||
private Map<String, Double> credits = new HashMap<String, Double>();
|
||||
|
||||
@Autowired
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void onSetUp() throws Exception {
|
||||
simpleJdbcTemplate.update("delete from TRADE");
|
||||
List<Map<String, Object>> list = simpleJdbcTemplate.queryForList("select name, CREDIT from customer");
|
||||
for (Map<String, Object> map : list) {
|
||||
credits.put((String) map.get("NAME"), ((Number) map.get("CREDIT")).doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
simpleJdbcTemplate.update("delete from TRADE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchJob() throws Exception{
|
||||
super.testLaunchJob();
|
||||
}
|
||||
|
||||
protected void validatePostConditions() {
|
||||
|
||||
// assertTrue(((Resource)applicationContext.getBean("customerFileLocator")).exists());
|
||||
|
||||
customers = Arrays.asList(new Customer("customer1", (credits.get("customer1") )),
|
||||
new Customer("customer2", (credits.get("customer2"))),
|
||||
new Customer("customer3", 100500),
|
||||
new Customer("customer4", credits.get("customer4")),
|
||||
new Customer("customer5", 32345),
|
||||
new Customer("customer6", 123456));
|
||||
|
||||
|
||||
// check content of the customer table
|
||||
activeRow = 0;
|
||||
simpleJdbcTemplate.getJdbcOperations().query(GET_CUSTOMERS, new RowCallbackHandler() {
|
||||
|
||||
public void processRow(ResultSet rs) throws SQLException {
|
||||
Customer customer = customers.get(activeRow++);
|
||||
|
||||
assertEquals(customer.getName(),rs.getString(1));
|
||||
assertEquals(customer.getCredit(), rs.getDouble(2), .01);
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(customers.size(), activeRow);
|
||||
|
||||
// check content of the output file
|
||||
}
|
||||
|
||||
protected void validatePreConditions() {
|
||||
assertTrue(((Resource)applicationContext.getBean("customerFileResource")).exists());
|
||||
}
|
||||
|
||||
private static class Customer {
|
||||
private String name;
|
||||
private double credit;
|
||||
|
||||
public Customer(String name, double credit) {
|
||||
this.name = name;
|
||||
this.credit = credit;
|
||||
}
|
||||
|
||||
public Customer(){
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the credit
|
||||
*/
|
||||
public double getCredit() {
|
||||
return credit;
|
||||
}
|
||||
/**
|
||||
* @param credit the credit to set
|
||||
*/
|
||||
public void setCredit(double credit) {
|
||||
this.credit = credit;
|
||||
}
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
final int PRIME = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(credit);
|
||||
result = PRIME * result + (int) (temp ^ (temp >>> 32));
|
||||
result = PRIME * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Customer other = (Customer) obj;
|
||||
if (Double.doubleToLongBits(credit) != Double.doubleToLongBits(other.credit))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.item.file.mapping.DefaultFieldSet;
|
||||
import org.springframework.batch.item.file.mapping.FieldSet;
|
||||
import org.springframework.batch.item.file.transform.LineTokenizer;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CompositeCustomerUpdateLineTokenizerTests {
|
||||
|
||||
StubLineTokenizer customerTokenizer;
|
||||
FieldSet customerFieldSet = new DefaultFieldSet(null);
|
||||
StubLineTokenizer footerTokenizer;
|
||||
FieldSet footerFieldSet = new DefaultFieldSet(null);
|
||||
CompositeCustomerUpdateLineTokenizer compositeTokenizer;
|
||||
|
||||
@Before
|
||||
public void init(){
|
||||
customerTokenizer = new StubLineTokenizer(customerFieldSet);
|
||||
footerTokenizer = new StubLineTokenizer(footerFieldSet);
|
||||
compositeTokenizer = new CompositeCustomerUpdateLineTokenizer();
|
||||
compositeTokenizer.setCustomerTokenizer(customerTokenizer);
|
||||
compositeTokenizer.setFooterTokenizer(footerTokenizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFooter(){
|
||||
|
||||
String footerLine = "Ffjkdalsfjdaskl;f";
|
||||
FieldSet fs = compositeTokenizer.tokenize(footerLine);
|
||||
assertEquals(footerFieldSet, fs);
|
||||
assertEquals(footerLine, footerTokenizer.getTokenizedLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomerAdd(){
|
||||
|
||||
String customerAddLine = "AFDASFDASFDFSA";
|
||||
FieldSet fs = compositeTokenizer.tokenize(customerAddLine);
|
||||
assertEquals(customerFieldSet, fs);
|
||||
assertEquals(customerAddLine, customerTokenizer.getTokenizedLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomerDelete(){
|
||||
|
||||
String customerAddLine = "DFDASFDASFDFSA";
|
||||
FieldSet fs = compositeTokenizer.tokenize(customerAddLine);
|
||||
assertEquals(customerFieldSet, fs);
|
||||
assertEquals(customerAddLine, customerTokenizer.getTokenizedLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomerUpdate(){
|
||||
|
||||
String customerAddLine = "UFDASFDASFDFSA";
|
||||
FieldSet fs = compositeTokenizer.tokenize(customerAddLine);
|
||||
assertEquals(customerFieldSet, fs);
|
||||
assertEquals(customerAddLine, customerTokenizer.getTokenizedLine());
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testInvalidLine(){
|
||||
|
||||
String invalidLine = "INVALID";
|
||||
compositeTokenizer.tokenize(invalidLine);
|
||||
}
|
||||
|
||||
|
||||
private static class StubLineTokenizer implements LineTokenizer{
|
||||
|
||||
private final FieldSet fieldSetToReturn;
|
||||
private String tokenizedLine;
|
||||
|
||||
public StubLineTokenizer(FieldSet fieldSetToReturn) {
|
||||
this.fieldSetToReturn = fieldSetToReturn;
|
||||
}
|
||||
|
||||
public FieldSet tokenize(String line) {
|
||||
this.tokenizedLine = line;
|
||||
return fieldSetToReturn;
|
||||
}
|
||||
|
||||
public String getTokenizedLine() {
|
||||
return tokenizedLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.springframework.batch.sample.domain.trade;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
import static org.springframework.batch.sample.domain.trade.CustomerOperation.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Lucas Ward
|
||||
*
|
||||
*/
|
||||
public class CustomerUpdateProcessorTests {
|
||||
|
||||
CustomerDao customerDao;
|
||||
InvalidCustomerLogger logger;
|
||||
CustomerUpdateProcessor processor;
|
||||
|
||||
@Before
|
||||
public void init(){
|
||||
customerDao = createMock(CustomerDao.class);
|
||||
logger = createMock(InvalidCustomerLogger.class);
|
||||
processor = new CustomerUpdateProcessor();
|
||||
processor.setCustomerDao(customerDao);
|
||||
processor.setInvalidCustomerLogger(logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAdd() throws Exception{
|
||||
|
||||
CustomerUpdate customerUpdate = new CustomerUpdate(ADD, "test customer", new BigDecimal(232.2));
|
||||
expect(customerDao.getCustomerByName("test customer")).andReturn(null);
|
||||
replay(customerDao);
|
||||
assertEquals(customerUpdate, processor.process(customerUpdate));
|
||||
verify(customerDao);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidAdd() throws Exception{
|
||||
|
||||
CustomerUpdate customerUpdate = new CustomerUpdate(ADD, "test customer", new BigDecimal(232.2));
|
||||
expect(customerDao.getCustomerByName("test customer")).andReturn(new CustomerCredit());
|
||||
logger.log(customerUpdate);
|
||||
replay(customerDao, logger);
|
||||
assertNull("Processor should return null", processor.process(customerUpdate));
|
||||
verify(customerDao, logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() throws Exception{
|
||||
//delete should never work, therefore, ensure it fails fast.
|
||||
CustomerUpdate customerUpdate = new CustomerUpdate(DELETE, "test customer", new BigDecimal(232.2));
|
||||
logger.log(customerUpdate);
|
||||
replay(customerDao, logger);
|
||||
assertNull("Processor should return null", processor.process(customerUpdate));
|
||||
verify(customerDao, logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulUpdate() throws Exception{
|
||||
|
||||
CustomerUpdate customerUpdate = new CustomerUpdate(UPDATE, "test customer", new BigDecimal(232.2));
|
||||
expect(customerDao.getCustomerByName("test customer")).andReturn(new CustomerCredit());
|
||||
replay(customerDao, logger);
|
||||
assertEquals(customerUpdate, processor.process(customerUpdate));
|
||||
verify(customerDao, logger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidUpdate() throws Exception{
|
||||
|
||||
CustomerUpdate customerUpdate = new CustomerUpdate(UPDATE, "test customer", new BigDecimal(232.2));
|
||||
expect(customerDao.getCustomerByName("test customer")).andReturn(null);
|
||||
logger.log(customerUpdate);
|
||||
replay(customerDao, logger);
|
||||
assertNull("Processor should return null", processor.process(customerUpdate));
|
||||
verify(customerDao, logger);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||
|
||||
<import resource="classpath:/simple-job-launcher-context.xml" />
|
||||
<import resource="classpath:/jobs/customerFilterJob.xml" />
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user