/*
* Copyright 2012 C24 Technologies
*
* 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 biz.c24.io.spring.batch.reader;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import org.junit.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.core.io.ClassPathResource;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.ValidationException;
import biz.c24.io.examples.models.basic.EmployeeElement;
import biz.c24.io.spring.batch.C24CompoundValidationException;
import biz.c24.io.spring.batch.reader.source.SplittingReaderSource;
import biz.c24.io.spring.batch.reader.source.FileSource;
import biz.c24.io.spring.batch.reader.source.ZipFileSource;
import biz.c24.io.spring.core.C24Model;
import biz.c24.io.spring.source.SourceFactory;
import biz.c24.io.spring.source.TextualSourceFactory;
import static org.mockito.Mockito.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
* Validate the C24ItemReader
*
* @author Andrew Elmore
*/
public class C24ItemReaderTests {
private C24Model employeeModel = new C24Model(EmployeeElement.getInstance());
private C24Model employeeXmlModel = new C24Model(biz.c24.io.examples.models.xml.EmployeeElement.getInstance());
@Test
public void testValidCsvRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employees-3-valid.csv"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source);
assertThat(objs.size(), is(3));
// Validation but no splitting
objs = readFile(employeeModel, null, null, true, source);
assertThat(objs.size(), is(3));
// Validation & splitting
objs = readFile(employeeModel, ".*", null, true, source);
assertThat(objs.size(), is(3));
}
@Test
public void testValidXmlRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employee-valid-noparent.xml"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeXmlModel, null, null, false, source);
assertThat(objs.size(), is(1));
}
@Test
public void testValidSplittingXmlRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employees-3-valid.xml"));
// Validation & splitting
Collection<ComplexDataObject> objs = readFile(employeeXmlModel, "^[ \t]*<employee .*", ".*/>.*", true, source);
assertThat(objs.size(), is(3));
}
@Test
public void testSourceFactory() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employees-3-valid.csv"));
TextualSourceFactory factory = new TextualSourceFactory();
factory.setEndOfDataRequired(false);
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source, factory);
assertThat(objs.size(), is(3));
// Validation but no splitting
objs = readFile(employeeModel, null, null, true, source, factory);
assertThat(objs.size(), is(3));
// Validation & splitting
objs = readFile(employeeModel, ".*", null, true, source, factory);
assertThat(objs.size(), is(3));
}
@Test
public void testSemanticallyInvalidCsvRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employees-3-semanticallyinvalid.csv"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source);
assertThat(objs.size(), is(3));
// Validation but no splitting
try {
runJob(new C24ItemReaderBuilder().model(employeeModel).source(source).quickValidate(), employeeModel);
fail("Semantically invalid file did not generate a ValidationException");
} catch(C24ValidationException pEx) {
// Expected behaviour
}
// Validation & splitting
try {
runJob(new C24ItemReaderBuilder().model(employeeModel).source(source).startPattern(".*").quickValidate(), employeeModel);
fail("Semantically invalid file did not generate a ValidationException");
} catch(C24ValidationException pEx) {
// Expected behaviour
}
// Try without failfast
try {
runJob(new C24ItemReaderBuilder().model(employeeModel).source(source).fullValidate(), employeeModel);
fail("Semantically invalid file did not generate a ValidationException");
} catch(C24CompoundValidationException cvEx) {
assertThat(cvEx.getFailures().size(), is(2));
} catch(C24ValidationException vEx) {
fail("Multiple validation errors did not generate a C24CompoundValidationException");
}
}
@Test
public void testStructurallyInvalidCsvRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setResource(new ClassPathResource("employees-3-structurallyinvalid.csv"));
// No validation, no splitting
try {
readFile(employeeModel, null, null, false, source);
fail("Structurally invalid file did not generate a ParserException");
} catch(ParseException uiEx) {
// Expected behaviour
}
// Validation but no splitting
try {
readFile(employeeModel, null, null, true, source);
fail("Structurally invalid file did not generate a ParserException");
} catch(ParseException uiEx) {
// Expected behaviour
}
// Validation & splitting
try {
readFile(employeeModel, ".*", null, true, source);
fail("Structurally invalid file did not generate a ParserException");
} catch(ParseException uiEx) {
// Expected behaviour
}
}
@Test
public void testValidZipRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
ZipFileSource source = new ZipFileSource();
source.setResource(new ClassPathResource("employees-5-valid.zip"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source);
assertThat(objs.size(), is(5));
// Validation but no splitting
objs = readFile(employeeModel, null, null, true, source);
assertThat(objs.size(), is(5));
assertThat(source.useMultipleThreadsPerReader(), is(true));
// Validation & splitting
objs = readFile(employeeModel, ".*", null, true, source);
assertThat(objs.size(), is(5));
assertThat(source.useMultipleThreadsPerReader(), is(true));
// Now give is a zip file with lots of small entries. Check that it encourages 1 thread per ZipEntry
source.setResource(new ClassPathResource("employees-50-valid.zip"));
objs = readFile(employeeModel, ".*", null, true, source);
assertThat(objs.size(), is(50));
assertThat(source.useMultipleThreadsPerReader(), is(false));
}
// Header Skipping Tests
@Test
public void testValidCsvHeaderRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
FileSource source = new FileSource();
source.setSkipLines(1);
source.setResource(new ClassPathResource("employees-3-valid-header.csv"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source);
assertThat(objs.size(), is(3));
// Validation but no splitting
objs = readFile(employeeModel, null, null, true, source);
assertThat(objs.size(), is(3));
// Validation & splitting
objs = readFile(employeeModel, ".*", null, true, source);
assertThat(objs.size(), is(3));
}
@Test
public void testValidZipHeaderRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException {
ZipFileSource source = new ZipFileSource();
source.setSkipLines(1);
source.setResource(new ClassPathResource("employees-5-valid-header.zip"));
// No validation, no splitting
Collection<ComplexDataObject> objs = readFile(employeeModel, null, null, false, source);
assertThat(objs.size(), is(5));
// Validation but no splitting
objs = readFile(employeeModel, null, null, true, source);
assertThat(objs.size(), is(5));
assertThat(source.useMultipleThreadsPerReader(), is(true));
// Validation & splitting
objs = readFile(employeeModel, ".*", null, true, source);
assertThat(objs.size(), is(5));
assertThat(source.useMultipleThreadsPerReader(), is(true));
}
private Collection<ComplexDataObject> readFile(C24Model model, String optionalElementStartRegEx, String optionalElementStopRegEx, boolean validate, SplittingReaderSource source) throws IOException, UnexpectedInputException, ParseException, NonTransientResourceException, ValidationException {
return readFile(model, optionalElementStartRegEx, optionalElementStopRegEx, validate, source, null);
}
private Collection<ComplexDataObject> runJob(C24ItemReader<ComplexDataObject> reader, C24Model model) throws IOException {
StepExecution stepExecution = getStepExecution();
reader.setup(stepExecution);
ComplexDataObject obj = null;
Collection<ComplexDataObject> objs = new LinkedList<ComplexDataObject>();
while((obj = reader.read()) != null) {
assertThat(obj.getDefiningElementDecl(), is(model.getRootElement()));
objs.add(obj);
}
reader.cleanup();
return objs;
}
private static class C24ItemReaderBuilder extends C24ItemReader<ComplexDataObject> {
public C24ItemReaderBuilder startPattern(String regex) {
setElementStartPattern(regex);
return this;
}
public C24ItemReaderBuilder stopPattern(String regex) {
setElementStopPattern(regex);
return this;
}
public C24ItemReaderBuilder factory(SourceFactory factory) {
setSourceFactory(factory);
return this;
}
public C24ItemReaderBuilder source(SplittingReaderSource source) {
setSource(source);
return this;
}
public C24ItemReaderBuilder model(C24Model model) {
setModel(model);
return this;
}
public C24ItemReaderBuilder quickValidate() {
setValidate(true);
return this;
}
public C24ItemReaderBuilder fullValidate() {
setValidate(true);
setFailfast(false);
return this;
}
}
private Collection<ComplexDataObject> readFile(C24Model model, String optionalElementStartRegEx, String optionalElementStopRegEx, boolean validate, SplittingReaderSource source, SourceFactory factory) throws IOException, UnexpectedInputException, ParseException, NonTransientResourceException, ValidationException {
C24ItemReader<ComplexDataObject> reader = new C24ItemReader<ComplexDataObject>();
reader.setModel(model);
if(optionalElementStartRegEx != null) {
reader.setElementStartPattern(optionalElementStartRegEx);
}
if(optionalElementStopRegEx != null) {
reader.setElementStopPattern(optionalElementStopRegEx);
}
if(factory != null) {
reader.setSourceFactory(factory);
}
reader.setSource(source);
reader.setValidate(validate);
return runJob(reader, model);
}
private StepExecution getStepExecution() throws IOException {
JobParameters jobParams = mock(JobParameters.class);
StepExecution stepExecution = mock(StepExecution.class);
when(stepExecution.getJobParameters()).thenReturn(jobParams);
return stepExecution;
}
}