/*
* Copyright 2008-2014 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.item.file;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.util.Comparator;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.file.mapping.PassThroughLineMapper;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link MultiResourceItemReader}.
*/
public class MultiResourceItemReaderIntegrationTests {
private MultiResourceItemReader<String> tested = new MultiResourceItemReader<String>();
private FlatFileItemReader<String> itemReader = new FlatFileItemReader<String>();
private ExecutionContext ctx = new ExecutionContext();
// test input spans several resources
private Resource r1 = new ByteArrayResource("1\n2\n3\n".getBytes());
private Resource r2 = new ByteArrayResource("4\n5\n".getBytes());
private Resource r3 = new ByteArrayResource("".getBytes());
private Resource r4 = new ByteArrayResource("6\n".getBytes());
private Resource r5 = new ByteArrayResource("7\n8\n".getBytes());
/**
* Setup the tested reader to read from the test resources.
*/
@Before
public void setUp() throws Exception {
itemReader.setLineMapper(new PassThroughLineMapper());
tested.setDelegate(itemReader);
tested.setComparator(new Comparator<Resource>() {
@Override
public int compare(Resource o1, Resource o2) {
return 0; // do not change ordering
}
});
tested.setResources(new Resource[] { r1, r2, r3, r4, r5 });
}
/**
* Read input from start to end.
*/
@Test
public void testRead() throws Exception {
tested.open(ctx);
assertEquals("1", tested.read());
assertEquals("2", tested.read());
assertEquals("3", tested.read());
assertEquals("4", tested.read());
assertEquals("5", tested.read());
assertEquals("6", tested.read());
assertEquals("7", tested.read());
assertEquals("8", tested.read());
assertEquals(null, tested.read());
tested.close();
}
@Test
public void testGetCurrentResource() throws Exception {
tested.open(ctx);
assertEquals("1", tested.read());
assertSame(r1, tested.getCurrentResource());
assertEquals("2", tested.read());
assertSame(r1, tested.getCurrentResource());
assertEquals("3", tested.read());
assertSame(r1, tested.getCurrentResource());
assertEquals("4", tested.read());
assertSame(r2, tested.getCurrentResource());
assertEquals("5", tested.read());
assertSame(r2, tested.getCurrentResource());
assertEquals("6", tested.read());
assertSame(r4, tested.getCurrentResource());
assertEquals("7", tested.read());
assertSame(r5, tested.getCurrentResource());
assertEquals("8", tested.read());
assertSame(r5, tested.getCurrentResource());
assertEquals(null, tested.read());
assertSame(null, tested.getCurrentResource());
tested.close();
}
@Test
public void testRestartWhenStateNotSaved() throws Exception {
tested.setSaveState(false);
tested.open(ctx);
assertEquals("1", tested.read());
tested.update(ctx);
assertEquals("2", tested.read());
assertEquals("3", tested.read());
tested.close();
tested.open(ctx);
assertEquals("1", tested.read());
}
/**
*
* Read items with a couple of rollbacks, requiring to jump back to items from previous resources.
*/
@Test
public void testRestartAcrossResourceBoundary() throws Exception {
tested.open(ctx);
assertEquals("1", tested.read());
tested.update(ctx);
assertEquals("2", tested.read());
assertEquals("3", tested.read());
tested.close();
tested.open(ctx);
assertEquals("2", tested.read());
assertEquals("3", tested.read());
assertEquals("4", tested.read());
tested.close();
tested.open(ctx);
assertEquals("2", tested.read());
assertEquals("3", tested.read());
assertEquals("4", tested.read());
assertEquals("5", tested.read());
tested.update(ctx);
assertEquals("6", tested.read());
assertEquals("7", tested.read());
tested.close();
tested.open(ctx);
assertEquals("6", tested.read());
assertEquals("7", tested.read());
assertEquals("8", tested.read());
assertEquals(null, tested.read());
tested.close();
}
/**
* Restore from saved state.
*/
@Test
public void testRestart() throws Exception {
tested.open(ctx);
assertEquals("1", tested.read());
assertEquals("2", tested.read());
assertEquals("3", tested.read());
assertEquals("4", tested.read());
tested.update(ctx);
assertEquals("5", tested.read());
assertEquals("6", tested.read());
tested.close();
tested.open(ctx);
assertEquals("5", tested.read());
assertEquals("6", tested.read());
assertEquals("7", tested.read());
assertEquals("8", tested.read());
assertEquals(null, tested.read());
}
/**
* Resources are ordered according to injected comparator.
*/
@Test
public void testResourceOrderingWithCustomComparator() {
Resource r1 = new ByteArrayResource("".getBytes(), "b");
Resource r2 = new ByteArrayResource("".getBytes(), "a");
Resource r3 = new ByteArrayResource("".getBytes(), "c");
Resource[] resources = new Resource[] { r1, r2, r3 };
Comparator<Resource> comp = new Comparator<Resource>() {
/**
* Reversed ordering by filename.
*/
@Override
public int compare(Resource o1, Resource o2) {
Resource r1 = o1;
Resource r2 = o2;
return -r1.getDescription().compareTo(r2.getDescription());
}
};
tested.setComparator(comp);
tested.setResources(resources);
tested.open(ctx);
resources = (Resource[]) ReflectionTestUtils.getField(tested, "resources");
assertSame(r3, resources[0]);
assertSame(r1, resources[1]);
assertSame(r2, resources[2]);
}
/**
* Empty resource list is OK.
*/
@Test
public void testNoResourcesFound() throws Exception {
tested.setResources(new Resource[] {});
tested.open(new ExecutionContext());
assertNull(tested.read());
tested.close();
}
/**
* Missing resource is OK.
*/
@Test
public void testNonExistentResources() throws Exception {
tested.setResources(new Resource[] { new FileSystemResource("no/such/file.txt") });
itemReader.setStrict(false);
tested.open(new ExecutionContext());
assertNull(tested.read());
tested.close();
}
/**
* Test {@link org.springframework.batch.item.ItemStream} lifecycle symmetry
*/
@Test
public void testNonExistentResourcesItemStreamLifecycle() throws Exception {
ItemStreamReaderImpl delegate = new ItemStreamReaderImpl();
tested.setDelegate(delegate);
tested.setResources(new Resource[] { });
itemReader.setStrict(false);
tested.open(new ExecutionContext());
assertNull(tested.read());
assertFalse(delegate.openCalled);
assertFalse(delegate.closeCalled);
assertFalse(delegate.updateCalled);
tested.close();
}
/**
* Directory resource behaves as if it was empty.
*/
@Test
public void testDirectoryResources() throws Exception {
FileSystemResource resource = new FileSystemResource("build/data");
resource.getFile().mkdirs();
assertTrue(resource.getFile().isDirectory());
tested.setResources(new Resource[] { resource });
itemReader.setStrict(false);
tested.open(new ExecutionContext());
assertNull(tested.read());
tested.close();
}
@Test
public void testMiddleResourceThrowsException() throws Exception {
Resource badResource = new AbstractResource() {
@Override
public InputStream getInputStream() throws IOException {
throw new RuntimeException();
}
@Override
public String getDescription() {
return null;
}
};
tested.setResources(new Resource[] { r1, badResource, r3, r4, r5 });
tested.open(ctx);
assertEquals("1", tested.read());
assertEquals("2", tested.read());
assertEquals("3", tested.read());
try {
assertEquals("4", tested.read());
fail();
}
catch (ItemStreamException ex) {
// a try/catch was used to ensure the exception was thrown when reading
// the 4th item, rather than on open
}
}
@Test
public void testFirstResourceThrowsExceptionOnRead() throws Exception {
Resource badResource = new AbstractResource() {
@Override
public InputStream getInputStream() throws IOException {
throw new RuntimeException();
}
@Override
public String getDescription() {
return null;
}
};
tested.setResources(new Resource[] { badResource, r2, r3, r4, r5 });
tested.open(ctx);
try {
assertEquals("1", tested.read());
fail();
}
catch (ItemStreamException ex) {
// a try/catch was used to ensure the exception was thrown when reading
// the 1st item, rather than on open
}
}
@Test
public void testBadIOInput() throws Exception {
Resource badResource = new AbstractResource() {
@Override
public boolean exists() {
// Looks good ...
return true;
}
@Override
public InputStream getInputStream() throws IOException {
// ... but fails during read
throw new RuntimeException();
}
@Override
public String getDescription() {
return null;
}
};
tested.setResources(new Resource[] { badResource, r2, r3, r4, r5 });
tested.open(ctx);
try {
assertEquals("1", tested.read());
fail();
}
catch (ItemStreamException ex) {
// expected
}
// Now check the next read gets the next resource
assertEquals("4", tested.read());
}
@Test
public void testGetCurrentResourceBeforeRead() throws Exception {
tested.open(ctx);
assertNull("There is no 'current' resource before read is called", tested.getCurrentResource());
}
/**
* No resources to read should result in error in strict mode.
*/
@Test(expected = IllegalStateException.class)
public void testStrictModeEnabled() throws Exception {
tested.setResources(new Resource[] {});
tested.setStrict(true);
tested.open(ctx);
}
/**
* No resources to read is OK when strict=false.
*/
@Test
public void testStrictModeDisabled() throws Exception {
tested.setResources(new Resource[] {});
tested.setStrict(false);
tested.open(ctx);
assertTrue("empty input doesn't cause an error", true);
}
/**
* E.g. when using the reader in the processing phase reading might not have been attempted at all before the job
* crashed (BATCH-1798).
*/
@Test
public void testRestartAfterFailureWithoutRead() throws Exception {
// save reader state without calling read
tested.open(ctx);
tested.update(ctx);
tested.close();
// restart should work OK
tested.open(ctx);
assertEquals("1", tested.read());
}
private static class ItemStreamReaderImpl implements ResourceAwareItemReaderItemStream<String> {
private boolean openCalled = false;
private boolean updateCalled = false;
private boolean closeCalled = false;
@Override
public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
return null;
}
@Override
public void open(ExecutionContext executionContext) throws ItemStreamException {
openCalled = true;
}
@Override
public void update(ExecutionContext executionContext) throws ItemStreamException {
updateCalled = true;
}
@Override
public void close() throws ItemStreamException {
closeCalled = true;
}
@Override
public void setResource(Resource resource) {
}
}
}