This page demonstrates how to use the CsvFieldValidator classes to validate a particular CSV String at parse time according to the rules you desire.
Note that any CSV file should be valid as long as it complies with RFC 4180. If you wish to process non-compliant CSV files, you will have to wait until the next release of the CSV Object Mapper.
For the purposes of this demo, let's say that we have a CSV file with the following content:
"First Name", "Last Name", "Street Address", "City", "State", "Zip Code", "Home Phone Number", "Cell Phone Number" "John", "Doe", "123 Test Dr.", "Test City", "HI", "11111", 1231231234, 5554443333 "Jane", "Doe", "321 Tset Dr.", "Tset City", "IA", "99999", 4324324321, 3334445555
Note that the Java Objects to which you map your CSV may be POJOs employing the standard [set/get]ter properties. However, your Objects may be much more "fancy" if you like.
For the purposes of this demo, we want to map to a Java Object that looks like this:
public class Person { private String firstName; private String lastName; private String streetAddress; private String city; private String state; private Integer zipCode; private String homePhoneNumber; private String cellPhoneNumber; public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName() { return lastName; } public void setStreetAddress(String streetAddress) { this.streetAddress = streetAddress; } public String getStreetAddress() { return streetAddress; } public void setCity(String city) { this.city = city; } public String getCity() { return city; } public void setState(String state) { this.state = state; } public String getState() { return state; } public void setZipCode(Integer zipCode) { this.zipCode = zipCode; } public Integer getZipCode() { return zipCode; } public void setHomePhoneNumber(String homePhoneNumber) { this.homePhoneNumber = homePhoneNumber; } public String getHomePhoneNumber() { return homePhoneNumber; } public void setCellPhoneNumber(String cellPhoneNumber) { this.cellPhoneNumber = cellPhoneNumber; } public String getCellPhoneNumber() { return cellPhoneNumber; } }
We also have a pre-existing mapping file to which we want to add validation definitions. It looks like this:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="csvToObjectMapper" class="com.projectnine.csvmapper.CsvToObjectMapper"> <property name="csvMappingDefinitions"> <map> <entry key="personMappingDefinition" value-ref="personMappingDefinition" /> </map> </property> </bean> <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition"> <property name="fieldMappings"> <list> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setFirstName(%ARGUMENT%)" /> <property name="columnIndex" value="0" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setLastName(%ARGUMENT%)" /> <property name="columnIndex" value="1" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setStreetAddress(%ARGUMENT%)" /> <property name="columnIndex" value="2" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setCity(%ARGUMENT%)" /> <property name="columnIndex" value="3" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setState(%ARGUMENT%)" /> <property name="columnIndex" value="4" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setZipCode(%ARGUMENT%)" /> <property name="columnIndex" value="5" /> <property name="formatter" ref="integerFormatter" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setHomePhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="6" /> <property name="formatter" ref="phoneNumberFormatter" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setCellPhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="7" /> <property name="formatter" ref="phoneNumberFormatter" /> </bean> </list> </property> <property name="beanClassName" value="Person" /> <property name="expectedNumberOfFields" value="8" /> </bean> <bean id="integerFormatter" class="com.projectnine.csvmapper.example.IntegerFormatter"> </bean> <bean id="phoneNumberFormatter" class="com.projectnine.csvmapper.examples.PhoneNumberFormatter"> <property name="separator" value="." /> </bean> </beans>
First of all, notice that the zipCode is not a String. We would of course never do this in real life, but at least I'm not using Cats and Dogs.
Anyway, before we attempt to convert the zipCode into an Integer, we might want to verify that all of the characters in the zipCode are numerical.
With regards to the phone numbers, we might want to do the same thing. Additionally, we want to make sure that a phone number is exactly ten characters long.
This is where the CsvFieldValidator comes in.
The configuration of a validator occurs in two parts: definition and usage. For instance, I would define my Integer validator as such:
... <bean id="numericalValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator"> <property name="regularExpressions"> <list> <value>\d+</value> </list> </property> </bean> ...
And I would use it in my mapping for the zipCode like so:
... <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setZipCode(%ARGUMENT%)" /> <property name="columnIndex" value="5" /> <property name="formatter" ref="integerFormatter" /> <property name="validationCommand" ref="numericalValidator" /> </bean> ...
It's a little more complicated, though, if you want to use more than one validator on the same value. For instance, I need to use the numericalValidator on my Phone Numbers, but I also need to verify that there are exactly ten digits in the phone number.
Now, this example is more complicated than it needs to be, but bear with me.
Here is my definition for my additional 10 character validator:
... <bean id="tenCharacterValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator"> <property name="regularExpressions"> <list> <value>.{10}</value> </list> </property> <property name="required" value="true" /> </bean> ...
But I need to use the 10 character validator in conjunction with the numerical validator. So I configure a validation chain:
... <bean id="phoneNumberValidation" class="org.apache.commons.chain.impl.ChainBase"> <constructor-arg> <list> <ref bean="tenCharacterValidator" /> <ref bean="numericalValidator" /> </list> </constructor-arg> </bean> ...
And adjust the field mapping for the phone numbers like so:
<bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setHomePhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="6" /> <property name="formatter" ref="phoneNumberFormatter" /> <property name="validationCommand" ref="phoneNumberValidation" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setCellPhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="7" /> <property name="formatter" ref="phoneNumberFormatter" /> <property name="validationCommand" ref="phoneNumberValidation" /> </bean>
One last note, the required property defaults to "false". That means that if a value is not set in the CSV, validation succeeds.
Fortunately for us, the com.projectnine.csvmapper.RegularExpressionCsvFieldValidator that we're using is already written. If it weren't, though, we would just extend the abstract com.projectnine.csvmapper.CsvFieldValidator and implement the doValidate method, a method that returns false if validation succeeds, returns true is validation succeeds and the rest of the chain should be aborted (if applicable), and throws an Exception if validation fails.
Here is the final configuration:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="csvToObjectMapper" class="com.projectnine.csvmapper.CsvToObjectMapper"> <property name="csvMappingDefinitions"> <map> <entry key="personMappingDefinition" value-ref="personMappingDefinition" /> </map> </property> </bean> <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition"> <property name="fieldMappings"> <list> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setFirstName(%ARGUMENT%)" /> <property name="columnIndex" value="0" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setLastName(%ARGUMENT%)" /> <property name="columnIndex" value="1" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setStreetAddress(%ARGUMENT%)" /> <property name="columnIndex" value="2" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setCity(%ARGUMENT%)" /> <property name="columnIndex" value="3" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setState(%ARGUMENT%)" /> <property name="columnIndex" value="4" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setZipCode(%ARGUMENT%)" /> <property name="columnIndex" value="5" /> <property name="formatter" ref="integerFormatter" /> <property name="validationCommand" ref="numericalValidator" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setHomePhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="6" /> <property name="formatter" ref="phoneNumberFormatter" /> <property name="validationCommand" ref="phoneNumberValidation" /> </bean> <bean class="com.projectnine.csvmapper.CsvFieldMapping"> <property name="csvToObjectExpression" value="setCellPhoneNumber(%ARGUMENT%)" /> <property name="columnIndex" value="7" /> <property name="formatter" ref="phoneNumberFormatter" /> <property name="validationCommand" ref="phoneNumberValidation" /> </bean> </list> </property> <property name="beanClassName" value="Person" /> <property name="expectedNumberOfFields" value="8" /> </bean> <bean id="integerFormatter" class="com.projectnine.csvmapper.example.IntegerFormatter"> </bean> <bean id="phoneNumberFormatter" class="com.projectnine.csvmapper.examples.PhoneNumberFormatter"> <property name="separator" value="." /> </bean> <bean id="tenCharacterValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator"> <property name="regularExpressions"> <list> <value>.{10}</value> </list> </property> <property name="required" value="true" /> </bean> <bean id="numericalValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator"> <property name="regularExpressions"> <list> <value>\d+</value> </list> </property> </bean> <bean id="phoneNumberValidation" class="org.apache.commons.chain.impl.ChainBase"> <constructor-arg> <list> <ref bean="tenCharacterValidator" /> <ref bean="numericalValidator" /> </list> </constructor-arg> </bean> </beans>