/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * <br> * http://www.apache.org/licenses/LICENSE-2.0 * <br> * 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.apache.activemq.artemis.tests.unit.core.client.impl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.MessageHandler; import org.apache.activemq.artemis.core.client.impl.ClientConsumerInternal; import org.apache.activemq.artemis.core.client.impl.ClientLargeMessageInternal; import org.apache.activemq.artemis.core.client.impl.ClientMessageInternal; import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal; import org.apache.activemq.artemis.core.client.impl.LargeMessageControllerImpl; import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQConsumerContext; import org.apache.activemq.artemis.spi.core.remoting.ConsumerContext; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.utils.ActiveMQBufferInputStream; import org.apache.activemq.artemis.utils.FutureLatch; import org.apache.activemq.artemis.utils.RandomUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class LargeMessageBufferTest extends ActiveMQTestBase { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- static int tmpFileCounter = 0; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- // Public -------------------------------------------------------- @Override @Before public void setUp() throws Exception { super.setUp(); tmpFileCounter++; File tmp = new File(getTestDir()); tmp.mkdirs(); } // Test Simple getBytes @Test public void testGetBytes() throws Exception { LargeMessageControllerImpl buffer = create15BytesSample(); for (int i = 1; i <= 15; i++) { try { Assert.assertEquals(i, buffer.readByte()); } catch (Exception e) { throw new Exception("Exception at position " + i, e); } } try { buffer.readByte(); Assert.fail("supposed to throw an exception"); } catch (IndexOutOfBoundsException e) { } } // Test for void getBytes(final int index, final byte[] dst) @Test public void testGetBytesIByteArray() throws Exception { LargeMessageControllerImpl buffer = create15BytesSample(); byte[] bytes = new byte[15]; buffer.getBytes(0, bytes); validateAgainstSample(bytes); try { buffer = create15BytesSample(); bytes = new byte[16]; buffer.getBytes(0, bytes); Assert.fail("supposed to throw an exception"); } catch (java.lang.IndexOutOfBoundsException e) { } } // testing void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) @Test public void testGetBytesILChannelBufferII() throws Exception { LargeMessageControllerImpl buffer = create15BytesSample(); ActiveMQBuffer dstBuffer = ActiveMQBuffers.fixedBuffer(20); dstBuffer.setIndex(0, 5); buffer.getBytes(0, dstBuffer); byte[] compareBytes = new byte[15]; dstBuffer.getBytes(5, compareBytes); validateAgainstSample(compareBytes); } // testing void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) @Test public void testReadIntegers() throws Exception { LargeMessageControllerImpl buffer = createBufferWithIntegers(3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); for (int i = 1; i <= 15; i++) { Assert.assertEquals(i, buffer.readInt()); } try { buffer.readByte(); Assert.fail("supposed to throw an exception"); } catch (IndexOutOfBoundsException e) { } } @Test public void testReadIntegersOverStream() throws Exception { LargeMessageControllerImpl buffer = createBufferWithIntegers(3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); ActiveMQBufferInputStream is = new ActiveMQBufferInputStream(buffer); DataInputStream dataInput = new DataInputStream(is); for (int i = 1; i <= 15; i++) { Assert.assertEquals(i, dataInput.readInt()); } assertEquals(-1, dataInput.read()); dataInput.close(); } // testing void getBytes(int index, ChannelBuffer dst, int dstIndex, int length) @Test public void testReadLongs() throws Exception { LargeMessageControllerImpl buffer = createBufferWithLongs(3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); for (int i = 1; i <= 15; i++) { Assert.assertEquals(i, buffer.readLong()); } try { buffer.readByte(); Assert.fail("supposed to throw an exception"); } catch (IndexOutOfBoundsException e) { } } @Test public void testReadLongsOverStream() throws Exception { LargeMessageControllerImpl buffer = createBufferWithLongs(3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); ActiveMQBufferInputStream is = new ActiveMQBufferInputStream(buffer); DataInputStream dataInput = new DataInputStream(is); for (int i = 1; i <= 15; i++) { Assert.assertEquals(i, dataInput.readLong()); } assertEquals(-1, dataInput.read()); dataInput.close(); } @Test public void testReadData() throws Exception { ActiveMQBuffer dynamic = ActiveMQBuffers.dynamicBuffer(1); String str1 = RandomUtil.randomString(); String str2 = RandomUtil.randomString(); double d1 = RandomUtil.randomDouble(); float f1 = RandomUtil.randomFloat(); dynamic.writeUTF(str1); dynamic.writeString(str2); dynamic.writeDouble(d1); dynamic.writeFloat(f1); LargeMessageControllerImpl readBuffer = splitBuffer(3, dynamic.toByteBuffer().array()); Assert.assertEquals(str1, readBuffer.readUTF()); Assert.assertEquals(str2, readBuffer.readString()); Assert.assertEquals(d1, readBuffer.readDouble(), 0.000001); Assert.assertEquals(f1, readBuffer.readFloat(), 0.000001); } private File getTestFile() { return new File(getTestDir(), "temp." + tmpFileCounter + ".file"); } @Test public void testReadDataOverCached() throws Exception { clearDataRecreateServerDirs(); ActiveMQBuffer dynamic = ActiveMQBuffers.dynamicBuffer(1); String str1 = RandomUtil.randomString(); String str2 = RandomUtil.randomString(); double d1 = RandomUtil.randomDouble(); float f1 = RandomUtil.randomFloat(); dynamic.writeUTF(str1); dynamic.writeString(str2); dynamic.writeDouble(d1); dynamic.writeFloat(f1); LargeMessageControllerImpl readBuffer = splitBuffer(3, dynamic.toByteBuffer().array(), getTestFile()); Assert.assertEquals(str1, readBuffer.readUTF()); Assert.assertEquals(str2, readBuffer.readString()); Assert.assertEquals(d1, readBuffer.readDouble(), 0.00000001); Assert.assertEquals(f1, readBuffer.readFloat(), 0.000001); readBuffer.readerIndex(0); Assert.assertEquals(str1, readBuffer.readUTF()); Assert.assertEquals(str2, readBuffer.readString()); Assert.assertEquals(d1, readBuffer.readDouble(), 0.00000001); Assert.assertEquals(f1, readBuffer.readFloat(), 0.000001); readBuffer.close(); } @Test public void testReadPartialData() throws Exception { final LargeMessageControllerImpl buffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 10, 10); buffer.addPacket(new byte[]{0, 1, 2, 3, 4}, 1, true); byte[] bytes = new byte[30]; buffer.readBytes(bytes, 0, 5); for (byte i = 0; i < 5; i++) { Assert.assertEquals(i, bytes[i]); } final CountDownLatch latchGo = new CountDownLatch(1); final AtomicInteger errorCount = new AtomicInteger(0); Thread t = new Thread() { @Override public void run() { try { latchGo.countDown(); buffer.readBytes(new byte[5]); } catch (IndexOutOfBoundsException ignored) { } catch (IllegalAccessError ignored) { } catch (Throwable e) { e.printStackTrace(); errorCount.incrementAndGet(); } } }; t.start(); ActiveMQTestBase.waitForLatch(latchGo); buffer.cancel(); t.join(); Assert.assertEquals(0, errorCount.get()); } @Test public void testInterruptData() throws Exception { LargeMessageControllerImpl readBuffer = splitBuffer(3, new byte[]{0, 1, 2, 3, 4}); byte[] bytes = new byte[30]; readBuffer.readBytes(bytes, 0, 5); for (byte i = 0; i < 5; i++) { Assert.assertEquals(i, bytes[i]); } } @Test public void testSplitBufferOnFile() throws Exception { LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 1024 * 1024, 1, getTestFile(), 1024); try { long count = 0; for (int i = 0; i < 10; i++) { byte[] buffer = new byte[10240]; for (int j = 0; j < 10240; j++) { buffer[j] = getSamplebyte(count++); } outBuffer.addPacket(buffer, 1, true); } outBuffer.readerIndex(0); for (int i = 0; i < 10240 * 10; i++) { assertEquals("position " + i, getSamplebyte(i), outBuffer.readByte()); } outBuffer.readerIndex(0); for (int i = 0; i < 10240 * 10; i++) { assertEquals("position " + i, getSamplebyte(i), outBuffer.readByte()); } } finally { outBuffer.close(); } } @Test public void testStreamData() throws Exception { final LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 1024 * 11 + 123, 1000); final PipedOutputStream output = new PipedOutputStream(); final PipedInputStream input = new PipedInputStream(output); final AtomicInteger errors = new AtomicInteger(0); // Done reading 3 elements final CountDownLatch done1 = new CountDownLatch(1); // Done with the thread final CountDownLatch done2 = new CountDownLatch(1); final AtomicInteger count = new AtomicInteger(0); final AtomicInteger totalBytes = new AtomicInteger(0); Thread treader = new Thread("treader") { @Override public void run() { try { byte[] line = new byte[1024]; int dataRead = 0; while (dataRead >= 0) { dataRead = input.read(line); if (dataRead > 0) { totalBytes.addAndGet(dataRead); if (count.incrementAndGet() == 3) { done1.countDown(); } } } } catch (Exception e) { e.printStackTrace(); errors.incrementAndGet(); } finally { done1.countDown(); done2.countDown(); } } }; treader.setDaemon(true); treader.start(); for (int i = 0; i < 3; i++) { outBuffer.addPacket(new byte[1024], 1, true); } outBuffer.setOutputStream(output); final CountDownLatch waiting = new CountDownLatch(1); Thread twaiter = new Thread("twaiter") { @Override public void run() { try { outBuffer.waitCompletion(0); waiting.countDown(); } catch (Exception e) { e.printStackTrace(); errors.incrementAndGet(); } } }; twaiter.setDaemon(true); twaiter.start(); Assert.assertTrue(done1.await(10, TimeUnit.SECONDS)); Assert.assertEquals(3, count.get()); Assert.assertEquals(1024 * 3, totalBytes.get()); for (int i = 0; i < 8; i++) { outBuffer.addPacket(new byte[1024], 1, true); } Assert.assertEquals(1, waiting.getCount()); outBuffer.addPacket(new byte[123], 1, false); Assert.assertTrue(done2.await(10, TimeUnit.SECONDS)); Assert.assertTrue(waiting.await(10, TimeUnit.SECONDS)); Assert.assertEquals(12, count.get()); Assert.assertEquals(1024 * 11 + 123, totalBytes.get()); treader.join(); twaiter.join(); Assert.assertEquals(0, errors.get()); input.close(); } @Test public void testStreamDataWaitCompletionOnCompleteBuffer() throws Exception { final LargeMessageControllerImpl outBuffer = create15BytesSample(); outBuffer.saveBuffer(new OutputStream() { @Override public void write(final int b) throws IOException { // nohting to be done } }); } @Test public void testStreamDataWaitCompletionOnInCompleteBuffer() throws Exception { final LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 5, 1000); class FakeOutputStream extends OutputStream { @Override public void write(int b) throws IOException { } } outBuffer.setOutputStream(new FakeOutputStream()); long time = System.currentTimeMillis(); try { outBuffer.waitCompletion(0); fail("supposed to throw an exception"); } catch (ActiveMQException e) { } assertTrue("It was supposed to wait at least 1 second", System.currentTimeMillis() - time > 1000); } @Test public void testStreamDataWaitCompletionOnSlowComingBuffer() throws Exception { final LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 5, 1000); class FakeOutputStream extends OutputStream { @Override public void write(int b) throws IOException { } } outBuffer.setOutputStream(new FakeOutputStream()); Thread sender = new Thread() { @Override public void run() { try { Thread.sleep(100); outBuffer.addPacket(new byte[]{0}, 1, true); Thread.sleep(100); outBuffer.addPacket(new byte[]{0}, 1, true); Thread.sleep(200); outBuffer.addPacket(new byte[]{0}, 1, false); } catch (Exception e) { } } }; sender.start(); assertTrue(outBuffer.waitCompletion(5000)); sender.join(); } @Test public void testErrorOnSetStreaming() throws Exception { long start = System.currentTimeMillis(); final LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), 5, 30000); outBuffer.addPacket(new byte[]{0, 1, 2, 3, 4}, 1, true); final CountDownLatch latchBytesWritten1 = new CountDownLatch(5); final CountDownLatch latchBytesWritten2 = new CountDownLatch(10); outBuffer.setOutputStream(new OutputStream() { @Override public void write(final int b) throws IOException { latchBytesWritten1.countDown(); latchBytesWritten2.countDown(); } }); ActiveMQTestBase.waitForLatch(latchBytesWritten1); try { outBuffer.readByte(); Assert.fail("supposed to throw an exception"); } catch (IllegalAccessError ignored) { } Assert.assertTrue("It waited too much", System.currentTimeMillis() - start < 30000); } @Test public void testReadBytesOnStreaming() throws Exception { byte[] byteArray = new byte[1024]; for (int i = 0; i < byteArray.length; i++) { byteArray[i] = getSamplebyte(i); } ActiveMQBuffer splitbuffer = splitBuffer(3, byteArray); ActiveMQBufferInputStream is = new ActiveMQBufferInputStream(splitbuffer); for (int i = 0; i < 100; i++) { assertEquals(getSamplebyte(i), (byte) is.read()); } for (int i = 100; i < byteArray.length; i += 10) { byte[] readBytes = new byte[10]; int size = is.read(readBytes); for (int j = 0; j < size; j++) { assertEquals(getSamplebyte(i + j), readBytes[j]); } } is.close(); } /** * @return */ private LargeMessageControllerImpl create15BytesSample() throws Exception { return splitBuffer(5, new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); } private LargeMessageControllerImpl createBufferWithIntegers(final int splitFactor, final int... values) throws Exception { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(values.length * 4); DataOutputStream dataOut = new DataOutputStream(byteOut); for (int value : values) { dataOut.writeInt(value); } return splitBuffer(splitFactor, byteOut.toByteArray()); } private LargeMessageControllerImpl createBufferWithLongs(final int splitFactor, final long... values) throws Exception { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(values.length * 8); DataOutputStream dataOut = new DataOutputStream(byteOut); for (long value : values) { dataOut.writeLong(value); } return splitBuffer(splitFactor, byteOut.toByteArray()); } private LargeMessageControllerImpl splitBuffer(final int splitFactor, final byte[] bytes) throws Exception { return splitBuffer(splitFactor, bytes, null); } private LargeMessageControllerImpl splitBuffer(final int splitFactor, final byte[] bytes, final File file) throws Exception { LargeMessageControllerImpl outBuffer = new LargeMessageControllerImpl(new FakeConsumerInternal(), bytes.length, 5000, file); ByteArrayInputStream input = new ByteArrayInputStream(bytes); while (true) { byte[] splitElement = new byte[splitFactor]; int size = input.read(splitElement); if (size <= 0) { break; } if (size < splitFactor) { byte[] newSplit = new byte[size]; System.arraycopy(splitElement, 0, newSplit, 0, size); outBuffer.addPacket(newSplit, 1, input.available() > 0); } else { outBuffer.addPacket(splitElement, 1, input.available() > 0); } } return outBuffer; } /** * @param bytes */ private void validateAgainstSample(final byte[] bytes) { for (int i = 1; i <= 15; i++) { Assert.assertEquals(i, bytes[i - 1]); } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- static class FakeConsumerInternal implements ClientConsumerInternal { @Override public ConsumerContext getConsumerContext() { return new ActiveMQConsumerContext(0); } @Override public void close() throws ActiveMQException { } @Override public Exception getLastException() { return null; } @Override public MessageHandler getMessageHandler() throws ActiveMQException { return null; } @Override public boolean isClosed() { return false; } @Override public ClientMessage receive() throws ActiveMQException { return null; } @Override public ClientMessage receive(final long timeout) throws ActiveMQException { return null; } @Override public ClientMessage receiveImmediate() throws ActiveMQException { return null; } @Override public FakeConsumerInternal setMessageHandler(final MessageHandler handler) throws ActiveMQException { return this; } @Override public void acknowledge(final ClientMessage message) throws ActiveMQException { } @Override public void individualAcknowledge(ClientMessage message) throws ActiveMQException { } @Override public void cleanUp() throws ActiveMQException { } @Override public void clear(boolean waitForOnMessage) throws ActiveMQException { } @Override public void clearAtFailover() { } @Override public void flowControl(final int messageBytes, final boolean discountSlowConsumer) throws ActiveMQException { } @Override public void flushAcks() throws ActiveMQException { } @Override public int getBufferSize() { return 0; } @Override public int getClientWindowSize() { return 0; } @Override public SimpleString getFilterString() { return null; } public long getID() { return 0; } @Override public SimpleString getQueueName() { return null; } @Override public boolean isBrowseOnly() { return false; } @Override public void handleMessage(ClientMessageInternal message) throws Exception { } @Override public void handleLargeMessage(ClientLargeMessageInternal clientLargeMessage, long largeMessageSize) throws Exception { } @Override public void handleLargeMessageContinuation(byte[] chunk, int flowControlSize, boolean isContinues) throws Exception { } @Override public void start() { } public void stop() throws ActiveMQException { } @Override public void stop(boolean waitForOnMessage) throws ActiveMQException { } @Override public ClientSession.QueueQuery getQueueInfo() { return null; } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.client.impl.ClientConsumerInternal#getNonXAsession() */ public ClientSessionInternal getSession() { return null; } /* (non-Javadoc) * @see org.apache.activemq.artemis.core.client.impl.ClientConsumerInternal#prepareForClose() */ @Override public Thread prepareForClose(FutureLatch future) throws ActiveMQException { return null; } } }