diff --git a/spring-batch-samples/.springBeans b/spring-batch-samples/.springBeans index 7c73069e8..367ebfe4a 100644 --- a/spring-batch-samples/.springBeans +++ b/spring-batch-samples/.springBeans @@ -1,7 +1,7 @@ 1 - + @@ -42,6 +42,7 @@ src/main/resources/org/springframework/batch/sample/config/common-context.xml src/main/resources/jobs/taskletJob.xml src/main/resources/jobs/headerFooterSample.xml + src/main/resources/jobs/customerFilterJob.xml @@ -357,5 +358,17 @@ src/main/resources/simple-job-launcher-context.xml + + + true + false + + src/main/resources/jobs/customerFilterJob.xml + src/main/resources/simple-job-launcher-context.xml + src/main/resources/data-source-context.xml + src/main/resources/data-source-context-init.xml + src/main/resources/org/springframework/batch/sample/config/common-context.xml + + diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizer.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizer.java new file mode 100644 index 000000000..d9ddc01f1 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizer.java @@ -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; + } +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerDao.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerDao.java new file mode 100644 index 000000000..c6535c7e0 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerDao.java @@ -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); +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerOperation.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerOperation.java new file mode 100644 index 000000000..cfc5037dd --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerOperation.java @@ -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 codeMap; + + private CustomerOperation(char code) { + this.code = code; + } + + static{ + codeMap = new HashMap(); + 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; + } +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdate.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdate.java new file mode 100644 index 000000000..fbdfb8ffe --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdate.java @@ -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 + "]"; + } +} \ No newline at end of file diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateFieldSetMapper.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateFieldSetMapper.java new file mode 100644 index 000000000..942952672 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateFieldSetMapper.java @@ -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 { + + 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; + } +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessor.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessor.java new file mode 100644 index 000000000..23e052d21 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessor.java @@ -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{ + + 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; + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateWriter.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateWriter.java new file mode 100644 index 000000000..ed8ebc819 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/CustomerUpdateWriter.java @@ -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 { + + private CustomerDao customerDao; + + public void write(List 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; + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/InvalidCustomerLogger.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/InvalidCustomerLogger.java new file mode 100644 index 000000000..c1025b98d --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/InvalidCustomerLogger.java @@ -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); + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/CommonsLoggingInvalidCustomerLogger.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/CommonsLoggingInvalidCustomerLogger.java new file mode 100644 index 000000000..84380d87c --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/CommonsLoggingInvalidCustomerLogger.java @@ -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 + "]"); + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/JdbcCustomerDao.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/JdbcCustomerDao.java new file mode 100644 index 000000000..3c9a45af6 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/trade/internal/JdbcCustomerDao.java @@ -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 customers = (List) 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}); + } + +} diff --git a/spring-batch-samples/src/main/resources/data/customerFilterJob/input/customers.txt b/spring-batch-samples/src/main/resources/data/customerFilterJob/input/customers.txt new file mode 100644 index 000000000..f50c5091e --- /dev/null +++ b/spring-batch-samples/src/main/resources/data/customerFilterJob/input/customers.txt @@ -0,0 +1,6 @@ +SCust Credit +Acustomer5 00032345 +Ucustomer3 00100500 +Acustomer6 00123456 +Dcustomer1 00000000 +F0000004 \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/jobs/customerFilterJob.xml b/spring-batch-samples/src/main/resources/jobs/customerFilterJob.xml new file mode 100644 index 000000000..8ef3362a4 --- /dev/null +++ b/spring-batch-samples/src/main/resources/jobs/customerFilterJob.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/CustomerFilterJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/CustomerFilterJobFunctionalTests.java new file mode 100644 index 000000000..bbe5edaa7 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/CustomerFilterJobFunctionalTests.java @@ -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 customers; + private int activeRow = 0; + + private SimpleJdbcTemplate simpleJdbcTemplate; + private Map credits = new HashMap(); + + @Autowired + public void setDataSource(DataSource dataSource) { + this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + } + + @Before + public void onSetUp() throws Exception { + simpleJdbcTemplate.update("delete from TRADE"); + List> list = simpleJdbcTemplate.queryForList("select name, CREDIT from customer"); + for (Map 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; + } + + + } + + +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizerTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizerTests.java new file mode 100644 index 000000000..33691cd49 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CompositeCustomerUpdateLineTokenizerTests.java @@ -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; + } + } +} diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessorTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessorTests.java new file mode 100644 index 000000000..36a10a163 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/domain/trade/CustomerUpdateProcessorTests.java @@ -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); + } + +} diff --git a/spring-batch-samples/src/test/resources/org/springframework/batch/sample/CustomerFilterJobFunctionalTests-context.xml b/spring-batch-samples/src/test/resources/org/springframework/batch/sample/CustomerFilterJobFunctionalTests-context.xml new file mode 100644 index 000000000..d8870f990 --- /dev/null +++ b/spring-batch-samples/src/test/resources/org/springframework/batch/sample/CustomerFilterJobFunctionalTests-context.xml @@ -0,0 +1,10 @@ + + + + + + +