/*
* 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
*
* 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.apache.activemq.artemis.core.client.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf;
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.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
import org.apache.activemq.artemis.api.core.ActiveMQLargeMessageInterruptedException;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle;
import org.apache.activemq.artemis.utils.ByteUtil;
import org.apache.activemq.artemis.utils.DataConstants;
import org.apache.activemq.artemis.utils.UTF8Util;
/**
* This class aggregates several {@link org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionReceiveContinuationMessage}
* as it was being handled
* by a single buffer. This buffer can be consumed as messages are arriving, and it will hold the
* packets until they are read using the ChannelBuffer interface, or the setOutputStream or
* saveStream are called.
*/
public class LargeMessageControllerImpl implements LargeMessageController {
// Constants -----------------------------------------------------
private static final String READ_ONLY_ERROR_MESSAGE = "This is a read-only buffer, setOperations are not supported";
// Attributes ----------------------------------------------------
private final ClientConsumerInternal consumerInternal;
private final LinkedBlockingQueue<LargeData> largeMessageData = new LinkedBlockingQueue<>();
private volatile LargeData currentPacket = null;
private final long totalSize;
private final int bufferSize;
private boolean streamEnded = false;
private boolean streamClosed = false;
private final long readTimeout;
private long readerIndex = 0;
/**
* This is to control if packets are arriving for a better timeout control
*/
private boolean packetAdded = false;
private long packetPosition = -1;
private long lastIndex = 0;
private long packetLastPosition = -1;
private OutputStream outStream;
// There's no need to wait a synchronization
// we just set the exception and let other threads to get it as soon as possible
private volatile Exception handledException;
private final FileCache fileCache;
private boolean local = false;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
public LargeMessageControllerImpl(final ClientConsumerInternal consumerInternal,
final long totalSize,
final long readTimeout) {
this(consumerInternal, totalSize, readTimeout, null);
}
public LargeMessageControllerImpl(final ClientConsumerInternal consumerInternal,
final long totalSize,
final long readTimeout,
final File cachedFile) {
this(consumerInternal, totalSize, readTimeout, cachedFile, 10 * 1024);
}
public LargeMessageControllerImpl(final ClientConsumerInternal consumerInternal,
final long totalSize,
final long readTimeout,
final File cachedFile,
final int bufferSize) {
this.consumerInternal = consumerInternal;
this.readTimeout = readTimeout;
this.totalSize = totalSize;
if (cachedFile == null) {
fileCache = null;
} else {
fileCache = new FileCache(cachedFile);
}
this.bufferSize = bufferSize;
}
// Public --------------------------------------------------------
public void setLocal(boolean local) {
this.local = local;
}
@Override
public void discardUnusedPackets() {
if (outStream == null) {
if (local)
return;
try {
checkForPacket(totalSize - 1);
} catch (Throwable ignored) {
}
}
}
/**
* TODO: move this to ConsumerContext as large message is a protocol specific thing
* Add a buff to the List, or save it to the OutputStream if set
*/
@Override
public void addPacket(byte[] chunk, int flowControlSize, boolean isContinues) {
int flowControlCredit = 0;
synchronized (this) {
packetAdded = true;
if (outStream != null) {
try {
if (!isContinues) {
streamEnded = true;
}
if (fileCache != null) {
fileCache.cachePackage(chunk);
}
outStream.write(chunk);
flowControlCredit = flowControlSize;
notifyAll();
if (streamEnded) {
outStream.close();
}
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorAddingPacket(e);
handledException = e;
}
} else {
if (fileCache != null) {
try {
fileCache.cachePackage(chunk);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorAddingPacket(e);
handledException = e;
}
}
largeMessageData.offer(new LargeData(chunk, flowControlSize, isContinues));
}
}
if (flowControlCredit != 0) {
try {
consumerInternal.flowControl(flowControlCredit, !isContinues);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorAddingPacket(e);
handledException = e;
}
}
}
@Override
public void cancel() {
this.handledException = ActiveMQClientMessageBundle.BUNDLE.largeMessageInterrupted();
synchronized (this) {
int totalSize = 0;
LargeData polledPacket = null;
while ((polledPacket = largeMessageData.poll()) != null) {
totalSize += polledPacket.getFlowControlSize();
}
try {
consumerInternal.flowControl(totalSize, false);
} catch (Exception ignored) {
// what else can we do here?
ActiveMQClientLogger.LOGGER.errorCallingCancel(ignored);
}
largeMessageData.offer(new LargeData());
streamEnded = true;
streamClosed = true;
notifyAll();
}
}
@Override
public synchronized void close() {
if (fileCache != null) {
fileCache.close();
}
}
@Override
public void setOutputStream(final OutputStream output) throws ActiveMQException {
int totalFlowControl = 0;
boolean continues = false;
synchronized (this) {
if (currentPacket != null) {
sendPacketToOutput(output, currentPacket);
currentPacket = null;
}
while (handledException == null) {
LargeData packet = largeMessageData.poll();
if (packet == null) {
break;
}
totalFlowControl += packet.getFlowControlSize();
continues = packet.isContinues();
sendPacketToOutput(output, packet);
}
checkException();
outStream = output;
}
if (totalFlowControl > 0) {
consumerInternal.flowControl(totalFlowControl, !continues);
}
}
@Override
public synchronized void saveBuffer(final OutputStream output) throws ActiveMQException {
if (streamClosed) {
throw ActiveMQClientMessageBundle.BUNDLE.largeMessageLostSession();
}
setOutputStream(output);
waitCompletion(0);
}
/**
* @param timeWait Milliseconds to Wait. 0 means forever
* @throws ActiveMQException
*/
@Override
public synchronized boolean waitCompletion(final long timeWait) throws ActiveMQException {
if (outStream == null) {
// There is no stream.. it will never achieve the end of streaming
return false;
}
long timeOut;
// If timeWait = 0, we will use the readTimeout
// And we will check if no packets have arrived within readTimeout milliseconds
if (timeWait != 0) {
timeOut = System.currentTimeMillis() + timeWait;
} else {
timeOut = System.currentTimeMillis() + readTimeout;
}
while (!streamEnded && handledException == null) {
try {
this.wait(timeWait == 0 ? readTimeout : timeWait);
} catch (InterruptedException e) {
throw new ActiveMQInterruptedException(e);
}
if (!streamEnded && handledException == null) {
if (timeWait != 0 && System.currentTimeMillis() > timeOut) {
throw ActiveMQClientMessageBundle.BUNDLE.timeoutOnLargeMessage();
} else if (System.currentTimeMillis() > timeOut && !packetAdded) {
throw ActiveMQClientMessageBundle.BUNDLE.timeoutOnLargeMessage();
}
}
}
checkException();
return streamEnded;
}
/**
* @throws ActiveMQException
*/
private void checkException() throws ActiveMQException {
// it's not needed to copy it as we never set it back to null
// once the exception is set, the controller is pretty much useless
if (handledException != null) {
if (handledException instanceof ActiveMQException) {
ActiveMQException nestedException;
// This is just to be user friendly and give the user a proper exception trace,
// instead to just where it was canceled.
if (handledException instanceof ActiveMQLargeMessageInterruptedException) {
nestedException = new ActiveMQLargeMessageInterruptedException(handledException.getMessage());
} else {
nestedException = new ActiveMQException(((ActiveMQException) handledException).getType(), handledException.getMessage());
}
nestedException.initCause(handledException);
throw nestedException;
} else {
throw new ActiveMQException(ActiveMQExceptionType.LARGE_MESSAGE_ERROR_BODY, "Error on saving LargeMessageBufferImpl", handledException);
}
}
}
// Channel Buffer Implementation ---------------------------------
@Override
public int capacity() {
return -1;
}
@Override
public byte readByte() {
return getByte(readerIndex++);
}
@Override
public byte getByte(final int index) {
return getByte((long) index);
}
private byte getByte(final long index) {
checkForPacket(index);
if (fileCache != null && index < packetPosition) {
return fileCache.getByteFromCache(index);
} else {
return currentPacket.getChunk()[(int) (index - packetPosition)];
}
}
@Override
public void getBytes(final int index, final ActiveMQBuffer dst, final int dstIndex, final int length) {
byte[] destBytes = new byte[length];
getBytes(index, destBytes);
dst.setBytes(dstIndex, destBytes);
}
private void getBytes(final long index, final ActiveMQBuffer dst, final int dstIndex, final int length) {
byte[] destBytes = new byte[length];
getBytes(index, destBytes);
dst.setBytes(dstIndex, destBytes);
}
@Override
public void getBytes(final int index, final byte[] dst, final int dstIndex, final int length) {
byte[] bytesToGet = new byte[length];
getBytes(index, bytesToGet);
System.arraycopy(bytesToGet, 0, dst, dstIndex, length);
}
public void getBytes(final long index, final byte[] dst, final int dstIndex, final int length) {
byte[] bytesToGet = new byte[length];
getBytes(index, bytesToGet);
System.arraycopy(bytesToGet, 0, dst, dstIndex, length);
}
@Override
public void getBytes(final int index, final ByteBuffer dst) {
byte[] bytesToGet = new byte[dst.remaining()];
getBytes(index, bytesToGet);
dst.put(bytesToGet);
}
public void getBytes(final long index, final ByteBuffer dst) {
byte[] bytesToGet = new byte[dst.remaining()];
getBytes(index, bytesToGet);
dst.put(bytesToGet);
}
public void getBytes(final int index, final OutputStream out, final int length) throws IOException {
byte[] bytesToGet = new byte[length];
getBytes(index, bytesToGet);
out.write(bytesToGet);
}
public void getBytes(final long index, final OutputStream out, final int length) throws IOException {
byte[] bytesToGet = new byte[length];
getBytes(index, bytesToGet);
out.write(bytesToGet);
}
public int getBytes(final int index, final GatheringByteChannel out, final int length) throws IOException {
byte[] bytesToGet = new byte[length];
getBytes(index, bytesToGet);
return out.write(ByteBuffer.wrap(bytesToGet));
}
@Override
public int getInt(final int index) {
return (getByte(index) & 0xff) << 24 | (getByte(index + 1) & 0xff) << 16 |
(getByte(index + 2) & 0xff) << 8 |
(getByte(index + 3) & 0xff) << 0;
}
public int getInt(final long index) {
return (getByte(index) & 0xff) << 24 | (getByte(index + 1) & 0xff) << 16 |
(getByte(index + 2) & 0xff) << 8 |
(getByte(index + 3) & 0xff) << 0;
}
@Override
public long getLong(final int index) {
return ((long) getByte(index) & 0xff) << 56 | ((long) getByte(index + 1) & 0xff) << 48 |
((long) getByte(index + 2) & 0xff) << 40 |
((long) getByte(index + 3) & 0xff) << 32 |
((long) getByte(index + 4) & 0xff) << 24 |
((long) getByte(index + 5) & 0xff) << 16 |
((long) getByte(index + 6) & 0xff) << 8 |
((long) getByte(index + 7) & 0xff) << 0;
}
public long getLong(final long index) {
return ((long) getByte(index) & 0xff) << 56 | ((long) getByte(index + 1) & 0xff) << 48 |
((long) getByte(index + 2) & 0xff) << 40 |
((long) getByte(index + 3) & 0xff) << 32 |
((long) getByte(index + 4) & 0xff) << 24 |
((long) getByte(index + 5) & 0xff) << 16 |
((long) getByte(index + 6) & 0xff) << 8 |
((long) getByte(index + 7) & 0xff) << 0;
}
@Override
public short getShort(final int index) {
return (short) (getByte(index) << 8 | getByte(index + 1) & 0xFF);
}
public short getShort(final long index) {
return (short) (getByte(index) << 8 | getByte(index + 1) & 0xFF);
}
private int getUnsignedMedium(final int index) {
return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | (getByte(index + 2) & 0xff) << 0;
}
public int getUnsignedMedium(final long index) {
return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | (getByte(index + 2) & 0xff) << 0;
}
@Override
public void setByte(final int index, final byte value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setBytes(final int index, final ActiveMQBuffer src, final int srcIndex, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setBytes(final int index, final byte[] src, final int srcIndex, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setBytes(final int index, final ByteBuffer src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setInt(final int index, final int value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setLong(final int index, final long value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setShort(final int index, final short value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ByteBuffer toByteBuffer(final int index, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void release() {
//no-op
}
@Override
public int readerIndex() {
return (int) readerIndex;
}
@Override
public void readerIndex(final int readerIndex) {
try {
checkForPacket(readerIndex);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorReadingIndex(e);
throw new RuntimeException(e.getMessage(), e);
}
this.readerIndex = readerIndex;
}
@Override
public int writerIndex() {
return (int) totalSize;
}
@Override
public long getSize() {
return totalSize;
}
@Override
public void writerIndex(final int writerIndex) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setIndex(final int readerIndex, final int writerIndex) {
try {
checkForPacket(readerIndex);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorSettingIndex(e);
throw new RuntimeException(e.getMessage(), e);
}
this.readerIndex = readerIndex;
}
@Override
public void clear() {
}
@Override
public boolean readable() {
return true;
}
@Override
public boolean writable() {
return false;
}
@Override
public int readableBytes() {
long readableBytes = totalSize - readerIndex;
if (readableBytes > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) (totalSize - readerIndex);
}
}
@Override
public int writableBytes() {
return 0;
}
@Override
public void markReaderIndex() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void resetReaderIndex() {
try {
checkForPacket(0);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorReSettingIndex(e);
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public void markWriterIndex() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void resetWriterIndex() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void discardReadBytes() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public short getUnsignedByte(final int index) {
return (short) (getByte(index) & 0xFF);
}
@Override
public int getUnsignedShort(final int index) {
return getShort(index) & 0xFFFF;
}
public int getMedium(final int index) {
int value = getUnsignedMedium(index);
if ((value & 0x800000) != 0) {
value |= 0xff000000;
}
return value;
}
@Override
public long getUnsignedInt(final int index) {
return getInt(index) & 0xFFFFFFFFL;
}
@Override
public void getBytes(int index, final byte[] dst) {
// TODO: optimize this by using System.arraycopy
for (int i = 0; i < dst.length; i++) {
dst[i] = getByte(index++);
}
}
public void getBytes(long index, final byte[] dst) {
// TODO: optimize this by using System.arraycopy
for (int i = 0; i < dst.length; i++) {
dst[i] = getByte(index++);
}
}
@Override
public void getBytes(final int index, final ActiveMQBuffer dst) {
getBytes(index, dst, dst.writableBytes());
}
@Override
public void getBytes(final int index, final ActiveMQBuffer dst, final int length) {
if (length > dst.writableBytes()) {
throw new IndexOutOfBoundsException();
}
getBytes(index, dst, dst.writerIndex(), length);
dst.writerIndex(dst.writerIndex() + length);
}
@Override
public void setBytes(final int index, final byte[] src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setBytes(final int index, final ActiveMQBuffer src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setBytes(final int index, final ActiveMQBuffer src, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public void setZero(final int index, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public int readUnsignedByte() {
return (short) (readByte() & 0xFF);
}
@Override
public short readShort() {
short v = getShort(readerIndex);
readerIndex += 2;
return v;
}
@Override
public int readUnsignedShort() {
return readShort() & 0xFFFF;
}
public int readMedium() {
int value = readUnsignedMedium();
if ((value & 0x800000) != 0) {
value |= 0xff000000;
}
return value;
}
public int readUnsignedMedium() {
int v = getUnsignedMedium(readerIndex);
readerIndex += 3;
return v;
}
@Override
public int readInt() {
int v = getInt(readerIndex);
readerIndex += 4;
return v;
}
public int readInt(final int pos) {
int v = getInt(pos);
return v;
}
@Override
public long readUnsignedInt() {
return readInt() & 0xFFFFFFFFL;
}
@Override
public long readLong() {
long v = getLong(readerIndex);
readerIndex += 8;
return v;
}
@Override
public void readBytes(final byte[] dst, final int dstIndex, final int length) {
getBytes(readerIndex, dst, dstIndex, length);
readerIndex += length;
}
@Override
public void readBytes(final byte[] dst) {
readBytes(dst, 0, dst.length);
}
@Override
public void readBytes(final ActiveMQBuffer dst) {
readBytes(dst, dst.writableBytes());
}
@Override
public void readBytes(final ActiveMQBuffer dst, final int length) {
if (length > dst.writableBytes()) {
throw new IndexOutOfBoundsException();
}
readBytes(dst, dst.writerIndex(), length);
dst.writerIndex(dst.writerIndex() + length);
}
@Override
public void readBytes(final ActiveMQBuffer dst, final int dstIndex, final int length) {
getBytes(readerIndex, dst, dstIndex, length);
readerIndex += length;
}
@Override
public void readBytes(final ByteBuffer dst) {
int length = dst.remaining();
getBytes(readerIndex, dst);
readerIndex += length;
}
public int readBytes(final GatheringByteChannel out, final int length) throws IOException {
int readBytes = getBytes((int) readerIndex, out, length);
readerIndex += readBytes;
return readBytes;
}
public void readBytes(final OutputStream out, final int length) throws IOException {
getBytes(readerIndex, out, length);
readerIndex += length;
}
@Override
public int skipBytes(final int length) {
long newReaderIndex = readerIndex + length;
checkForPacket(newReaderIndex);
readerIndex = newReaderIndex;
return length;
}
@Override
public void writeByte(final byte value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeShort(final short value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public void writeMedium(final int value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeInt(final int value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeLong(final long value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeBytes(final byte[] src, final int srcIndex, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeBytes(final byte[] src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public void writeBytes(final ActiveMQBuffer src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeBytes(final ActiveMQBuffer src, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeBytes(final ByteBuffer src) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
/**
* Transfers the specified source buffer's data to this buffer starting at
* the current {@code writerIndex} until the source buffer's position
* reaches its limit, and increases the {@code writerIndex} by the
* number of the transferred bytes.
*
* @param src The source buffer
* @throws IndexOutOfBoundsException if {@code src.remaining()} is greater than
* {@code this.writableBytes}
*/
@Override
public void writeBytes(ByteBuf src, int srcIndex, int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public int writeBytes(final InputStream in, final int length) throws IOException {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public int writeBytes(final ScatteringByteChannel in, final int length) throws IOException {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public void writeZero(final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ByteBuffer toByteBuffer() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public ByteBuffer[] toByteBuffers() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public ByteBuffer[] toByteBuffers(final int index, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public String toString(final String charsetName) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
public Object getUnderlyingBuffer() {
return this;
}
@Override
public boolean readBoolean() {
return readByte() != 0;
}
@Override
public char readChar() {
return (char) readShort();
}
@Override
public char getChar(final int index) {
return (char) getShort(index);
}
@Override
public double getDouble(final int index) {
return Double.longBitsToDouble(getLong(index));
}
@Override
public float getFloat(final int index) {
return Float.intBitsToFloat(getInt(index));
}
@Override
public ActiveMQBuffer readBytes(final int length) {
byte[] bytesToGet = new byte[length];
getBytes(readerIndex, bytesToGet);
readerIndex += length;
return ActiveMQBuffers.wrappedBuffer(bytesToGet);
}
@Override
public double readDouble() {
return Double.longBitsToDouble(readLong());
}
@Override
public float readFloat() {
return Float.intBitsToFloat(readInt());
}
@Override
public SimpleString readNullableSimpleString() {
int b = readByte();
if (b == DataConstants.NULL) {
return null;
} else {
return readSimpleString();
}
}
@Override
public String readNullableString() {
int b = readByte();
if (b == DataConstants.NULL) {
return null;
} else {
return readString();
}
}
@Override
public SimpleString readSimpleString() {
int len = readInt();
byte[] data = new byte[len];
readBytes(data);
return new SimpleString(data);
}
@Override
public String readString() {
int len = readInt();
if (len < 9) {
char[] chars = new char[len];
for (int i = 0; i < len; i++) {
chars[i] = (char) readShort();
}
return new String(chars);
} else if (len < 0xfff) {
return readUTF();
} else {
return readSimpleString().toString();
}
}
@Override
public String readUTF() {
return UTF8Util.readUTF(this);
}
@Override
public void writeBoolean(final boolean val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeChar(final char val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeDouble(final double val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeFloat(final float val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeNullableSimpleString(final SimpleString val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeNullableString(final String val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeSimpleString(final SimpleString val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeString(final String val) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeUTF(final String utf) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ActiveMQBuffer copy() {
throw new UnsupportedOperationException();
}
@Override
public ActiveMQBuffer slice(final int index, final int length) {
throw new UnsupportedOperationException();
}
/**
* @param output
* @param packet
* @throws ActiveMQException
*/
private void sendPacketToOutput(final OutputStream output, final LargeData packet) throws ActiveMQException {
try {
output.write(packet.getChunk());
if (!packet.isContinues()) {
streamEnded = true;
output.close();
}
} catch (IOException e) {
throw ActiveMQClientMessageBundle.BUNDLE.errorWritingLargeMessage(e);
}
}
private void popPacket() {
try {
if (streamEnded) {
// no more packets, we are over the last one already
throw new IndexOutOfBoundsException();
}
int sizeToAdd = currentPacket != null ? currentPacket.chunk.length : 1;
currentPacket = largeMessageData.poll(readTimeout, TimeUnit.MILLISECONDS);
if (currentPacket == null) {
throw new IndexOutOfBoundsException();
}
if (currentPacket.chunk == null) { // Empty packet as a signal to interruption
currentPacket = null;
streamEnded = true;
throw new IndexOutOfBoundsException();
}
consumerInternal.flowControl(currentPacket.getFlowControlSize(), !currentPacket.isContinues());
packetPosition += sizeToAdd;
packetLastPosition = packetPosition + currentPacket.getChunk().length;
} catch (IndexOutOfBoundsException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void checkForPacket(final long index) {
if (outStream != null) {
throw new IllegalAccessError("Can't read the messageBody after setting outputStream");
}
if (index >= totalSize) {
throw new IndexOutOfBoundsException();
}
if (streamClosed) {
throw new IllegalAccessError("The consumer associated with this large message was closed before the body was read");
}
if (fileCache == null) {
if (index < lastIndex) {
throw new IllegalAccessError("LargeMessage have read-only and one-way buffers");
}
lastIndex = index;
}
while (index >= packetLastPosition && !streamEnded) {
popPacket();
}
}
private final class FileCache {
private FileCache(final File cachedFile) {
this.cachedFile = cachedFile;
}
ByteBuffer readCache;
long readCachePositionStart = Integer.MAX_VALUE;
long readCachePositionEnd = -1;
private final File cachedFile;
private volatile RandomAccessFile cachedRAFile;
private volatile FileChannel cachedChannel;
private synchronized void readCache(final long position) {
try {
if (position < readCachePositionStart || position > readCachePositionEnd) {
checkOpen();
if (position > cachedChannel.size()) {
throw new ArrayIndexOutOfBoundsException("position > " + cachedChannel.size());
}
readCachePositionStart = position / bufferSize * bufferSize;
cachedChannel.position(readCachePositionStart);
if (readCache == null) {
readCache = ByteBuffer.allocate(bufferSize);
}
readCache.clear();
readCachePositionEnd = readCachePositionStart + cachedChannel.read(readCache) - 1;
}
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorReadingCache(e);
throw new RuntimeException(e.getMessage(), e);
} finally {
close();
}
}
public synchronized byte getByteFromCache(final long position) {
readCache(position);
return readCache.get((int) (position - readCachePositionStart));
}
public void cachePackage(final byte[] body) throws Exception {
checkOpen();
cachedChannel.position(cachedChannel.size());
cachedChannel.write(ByteBuffer.wrap(body));
close();
}
/**
* @throws FileNotFoundException
*/
public void checkOpen() throws FileNotFoundException {
if (cachedFile != null || !cachedChannel.isOpen()) {
cachedRAFile = new RandomAccessFile(cachedFile, "rw");
cachedChannel = cachedRAFile.getChannel();
}
}
public void close() {
if (cachedChannel != null && cachedChannel.isOpen()) {
try {
cachedChannel.close();
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorClosingCache(e);
}
cachedChannel = null;
}
if (cachedRAFile != null) {
try {
cachedRAFile.close();
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorClosingCache(e);
}
cachedRAFile = null;
}
}
@Override
protected void finalize() {
close();
if (cachedFile != null && cachedFile.exists()) {
try {
cachedFile.delete();
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.errorFinalisingCache(e);
}
}
}
}
/**
* from {@link java.io.DataInput} interface
*/
@Override
public void readFully(byte[] b) throws IOException {
readBytes(b);
}
/**
* from {@link java.io.DataInput} interface
*/
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
readBytes(b, off, len);
}
/**
* from {@link java.io.DataInput} interface
*/
@Override
public String readLine() throws IOException {
return ByteUtil.readLine(this);
}
@Override
public ByteBuf byteBuf() {
return null;
}
@Override
public ActiveMQBuffer copy(final int index, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ActiveMQBuffer duplicate() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ActiveMQBuffer readSlice(final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setChar(final int index, final char value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setDouble(final int index, final double value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void setFloat(final int index, final float value) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public ActiveMQBuffer slice() {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
@Override
public void writeBytes(final ActiveMQBuffer src, final int srcIndex, final int length) {
throw new IllegalAccessError(LargeMessageControllerImpl.READ_ONLY_ERROR_MESSAGE);
}
private static class LargeData {
final byte[] chunk;
final int flowControlSize;
final boolean continues;
private LargeData() {
continues = false;
flowControlSize = 0;
chunk = null;
}
private LargeData(byte[] chunk, int flowControlSize, boolean continues) {
this.chunk = chunk;
this.flowControlSize = flowControlSize;
this.continues = continues;
}
public byte[] getChunk() {
return chunk;
}
public int getFlowControlSize() {
return flowControlSize;
}
public boolean isContinues() {
return continues;
}
}
}