/* * 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 static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.Collection; import java.util.Collections; 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.ItemReader; 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.reader.source.SplittingReaderSource; import biz.c24.io.spring.batch.reader.source.ZipFileSource; import biz.c24.io.spring.core.C24Model; import biz.c24.io.spring.source.SourceFactory; /** * Validate the C24ItemReader works correctly in parallel * * @author Andrew Elmore * */ public class C24ItemReaderParallelTests { private C24Model employeeModel = new C24Model(EmployeeElement.getInstance()); private C24Model employeeXmlModel = new C24Model(biz.c24.io.examples.models.xml.EmployeeElement.getInstance()); @Test public void testValidZipSingleRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException { ZipFileSource source = new ZipFileSource(); source.setResource(new ClassPathResource("employees-100-valid-individual-noparent.xml.zip")); // No validation, no splitting // Unsupported due to XMLSource not detecting it has no data before it starts parsing // As it caches data internally we can't make the decision for it Collection<ComplexDataObject> objs = readFile(employeeXmlModel, null, null, false, source); assertThat(objs.size(), is(100)); // Validation but no splitting objs = readFile(employeeXmlModel, null, null, true, source); assertThat(objs.size(), is(100)); assertThat(source.useMultipleThreadsPerReader(), is(false)); // Validation & splitting - start pattern only objs = readFile(employeeXmlModel, ".*<employee.*", null, true, source); assertThat(objs.size(), is(100)); assertThat(source.useMultipleThreadsPerReader(), is(false)); // Validation & splitting - start & stop patterns objs = readFile(employeeXmlModel, ".*<employee.*", ".*/>.*", true, source); assertThat(objs.size(), is(100)); assertThat(source.useMultipleThreadsPerReader(), is(false)); } @Test public void testValidZipCombinedRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException { ZipFileSource source = new ZipFileSource(); source.setResource(new ClassPathResource("employees-1500-valid-combined.xml.zip")); // Validation & splitting - start pattern only Collection<ComplexDataObject> objs = readFile(employeeXmlModel, ".*<employee .*", ".*/>.*", true, source); assertThat(objs.size(), is(1500)); assertThat(source.useMultipleThreadsPerReader(), is(true)); } @Test public void testValidZipCombinedNoParentRead() throws UnexpectedInputException, ParseException, NonTransientResourceException, IOException, ValidationException { ZipFileSource source = new ZipFileSource(); source.setResource(new ClassPathResource("employees-1500-valid-combined-noparent.xml.zip")); // Validation & splitting - start pattern only Collection<ComplexDataObject> objs = readFile(employeeXmlModel, ".*<employee .*", null, true, source); assertThat(objs.size(), is(1500)); assertThat(source.useMultipleThreadsPerReader(), is(true)); // Validation & splitting - start & stop patterns objs = readFile(employeeXmlModel, ".*<employee .*", ".*/>.*", true, source); assertThat(objs.size(), is(1500)); 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> 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); StepExecution stepExecution = getStepExecution(); reader.setup(stepExecution); Collection<ComplexDataObject> objs = Collections.synchronizedList(new LinkedList<ComplexDataObject>()); Thread[] threads = new Thread[8]; for(int i=0; i < threads.length; i++) { threads[i] = new Thread(new Worker<ComplexDataObject>(model, objs, reader)); threads[i].start(); } for(int i=0; i < threads.length; i++) { try { threads[i].join(20000); } catch (InterruptedException e) { throw new RuntimeException("Interrupted waiting on thread"); } if(threads[i].isAlive()) { throw new RuntimeException("Timed out waiting for thread to complete its work"); } } reader.cleanup(); return objs; } private static class Worker<T extends ComplexDataObject> implements Runnable { private C24Model model; private Collection<T> collection; private ItemReader<T> reader; public Worker(C24Model model, Collection<T> collection, ItemReader<T> itemReader) { this.model = model; this.collection = collection; this.reader = itemReader; } public void run() { T obj = null; try { while((obj = reader.read()) != null) { assertThat(obj.getDefiningElementDecl(), is(model.getRootElement())); collection.add(obj); } } catch (Exception e) { throw new RuntimeException(e); } } } private StepExecution getStepExecution() throws IOException { JobParameters jobParams = mock(JobParameters.class); StepExecution stepExecution = mock(StepExecution.class); when(stepExecution.getJobParameters()).thenReturn(jobParams); return stepExecution; } }