/* * Copyright 2006-2012 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.support.transaction; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Dave Syer * @author Michael Minella * @author Will Schipp * */ public class TransactionAwareBufferedWriterTests { private FileChannel fileChannel; private TransactionAwareBufferedWriter writer; @Before public void init() { fileChannel = mock(FileChannel.class); writer = new TransactionAwareBufferedWriter(fileChannel, () -> { try { ByteBuffer bb = ByteBuffer.wrap("c".getBytes()); fileChannel.write(bb); } catch (IOException e) { throw new IllegalStateException(e); } }); writer.setEncoding("UTF-8"); } private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); /** * Test method for * {@link org.springframework.batch.support.transaction.TransactionAwareBufferedWriter#write(java.lang.String)} * . */ @Test public void testWriteOutsideTransaction() throws Exception { ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(3); writer.write("foo"); writer.flush(); // Not closed yet String s = getStringFromByteBuffer(bb.getValue()); assertEquals("foo", s); verify(fileChannel, never()).force(false); } @Test public void testWriteOutsideTransactionForceSync() throws Exception { writer.setForceSync(true); ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(3); writer.write("foo"); writer.flush(); // Not closed yet String s = getStringFromByteBuffer(bb.getValue()); assertEquals("foo", s); verify(fileChannel, times(1)).force(false); } @Test public void testBufferSizeOutsideTransaction() throws Exception { ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(3); writer.write("foo"); assertEquals(0, writer.getBufferSize()); } @Test public void testCloseOutsideTransaction() throws Exception { ArgumentCaptor<ByteBuffer> byteBufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(byteBufferCaptor.capture())).thenAnswer(invocation -> ((ByteBuffer) invocation.getArguments()[0]).remaining()); writer.write("foo"); writer.close(); assertEquals("foo", getStringFromByteBuffer(byteBufferCaptor.getAllValues().get(0))); assertEquals("c", getStringFromByteBuffer(byteBufferCaptor.getAllValues().get(1))); } @Test public void testFlushInTransaction() throws Exception { when(fileChannel.write(any(ByteBuffer.class))).thenReturn(3); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); writer.flush(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(3, writer.getBufferSize()); return null; }); verify(fileChannel, never()).force(false); } @Test public void testFlushInTransactionForceSync() throws Exception { writer.setForceSync(true); when(fileChannel.write(any(ByteBuffer.class))).thenReturn(3); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); writer.flush(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(3, writer.getBufferSize()); return null; }); verify(fileChannel, times(1)).force(false); } @Test public void testWriteWithCommit() throws Exception { ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(3); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(3, writer.getBufferSize()); return null; }); assertEquals(0, writer.getBufferSize()); } @Test public void testBufferSizeInTransaction() throws Exception { ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(3); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(3, writer.getBufferSize()); return null; }); assertEquals(0, writer.getBufferSize()); } @Test // BATCH-1959 public void testBufferSizeInTransactionWithMultiByteCharacterUTF8() throws Exception { ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(5); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("fóó"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(5, writer.getBufferSize()); return null; }); assertEquals(0, writer.getBufferSize()); } @Test // BATCH-1959 public void testBufferSizeInTransactionWithMultiByteCharacterUTF16BE() throws Exception { writer.setEncoding("UTF-16BE"); ArgumentCaptor<ByteBuffer> bb = ArgumentCaptor.forClass(ByteBuffer.class); when(fileChannel.write(bb.capture())).thenReturn(6); new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("fóó"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } assertEquals(6, writer.getBufferSize()); return null; }); assertEquals(0, writer.getBufferSize()); } @Test public void testWriteWithRollback() throws Exception { try { new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } throw new RuntimeException("Planned failure"); }); fail("Exception was not thrown"); } catch (RuntimeException e) { // expected String message = e.getMessage(); assertEquals("Wrong message: " + message, "Planned failure", message); } assertEquals(0, writer.getBufferSize()); } @Test public void testCleanUpAfterRollback() throws Exception { testWriteWithRollback(); testWriteWithCommit(); } @Test public void testExceptionOnFlush() throws Exception { writer = new TransactionAwareBufferedWriter(fileChannel, () -> { }); try { new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { writer.write("foo"); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } return null; }); fail("Exception was not thrown"); } catch (FlushFailedException ffe) { assertEquals("Could not write to output buffer", ffe.getMessage()); } } // BATCH-2018 @Test public void testResourceKeyCollision() throws Exception { final int limit = 5000; final TransactionAwareBufferedWriter[] writers = new TransactionAwareBufferedWriter[limit]; final String[] results = new String[limit]; for(int i = 0; i< limit; i++) { final int index = i; @SuppressWarnings("resource") FileChannel fileChannel = mock(FileChannel.class); when(fileChannel.write(any(ByteBuffer.class))).thenAnswer(invocation -> { ByteBuffer buffer = (ByteBuffer) invocation.getArguments()[0]; String val = new String(buffer.array(), "UTF-8"); if(results[index] == null) { results[index] = val; } else { results[index] += val; } return buffer.limit(); }); writers[i] = new TransactionAwareBufferedWriter(fileChannel, null); } new TransactionTemplate(transactionManager).execute((TransactionCallback<Void>) status -> { try { for(int i=0; i< limit; i++) { writers[i].write(String.valueOf(i)); } } catch (IOException e) { throw new IllegalStateException("Unexpected IOException", e); } return null; }); for(int i=0; i< limit; i++) { assertEquals(String.valueOf(i), results[i]); } } private String getStringFromByteBuffer(ByteBuffer bb) { byte[] bytearr = new byte[bb.remaining()]; bb.get(bytearr); return new String(bytearr); } }