com.rabbitmq.client.impl.CommandAssembler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amqp-client Show documentation
Show all versions of amqp-client Show documentation
The RabbitMQ Java client library allows Java applications to interface with RabbitMQ.
// Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.client.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.UnexpectedFrameError;
import static java.lang.String.format;
/**
* Class responsible for piecing together a command from a series of {@link Frame}s.
* Concurrency
* This class is thread-safe, since all methods are synchronised. Callers should not
* synchronise on objects of this class unless they are sole owners.
* @see AMQCommand
*/
final class CommandAssembler {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** Current state, used to decide how to handle each incoming frame. */
private enum CAState {
EXPECTING_METHOD, EXPECTING_CONTENT_HEADER, EXPECTING_CONTENT_BODY, COMPLETE
}
private CAState state;
/** The method for this command */
private Method method;
/** The content header for this command */
private AMQContentHeader contentHeader;
/** The fragments of this command's content body - a list of byte[] */
private final List bodyN;
/** sum of the lengths of all fragments */
private int bodyLength;
/** No bytes of content body not yet accumulated */
private long remainingBodyBytes;
private final int maxBodyLength;
public CommandAssembler(Method method, AMQContentHeader contentHeader, byte[] body,
int maxBodyLength) {
this.method = method;
this.contentHeader = contentHeader;
this.bodyN = new ArrayList<>(2);
this.bodyLength = 0;
this.remainingBodyBytes = 0;
this.maxBodyLength = maxBodyLength;
appendBodyFragment(body);
if (method == null) {
this.state = CAState.EXPECTING_METHOD;
} else if (contentHeader == null) {
this.state = method.hasContent() ? CAState.EXPECTING_CONTENT_HEADER : CAState.COMPLETE;
} else {
this.remainingBodyBytes = contentHeader.getBodySize() - this.bodyLength;
updateContentBodyState();
}
}
public synchronized Method getMethod() {
return this.method;
}
public synchronized AMQContentHeader getContentHeader() {
return this.contentHeader;
}
/** @return true if the command is complete */
public synchronized boolean isComplete() {
return (this.state == CAState.COMPLETE);
}
/** Decides whether more body frames are expected */
private void updateContentBodyState() {
this.state = (this.remainingBodyBytes > 0) ? CAState.EXPECTING_CONTENT_BODY : CAState.COMPLETE;
}
private void consumeMethodFrame(Frame f) throws IOException {
if (f.type == AMQP.FRAME_METHOD) {
this.method = AMQImpl.readMethodFrom(f.getInputStream());
this.state = this.method.hasContent() ? CAState.EXPECTING_CONTENT_HEADER : CAState.COMPLETE;
} else {
throw new UnexpectedFrameError(f, AMQP.FRAME_METHOD);
}
}
private void consumeHeaderFrame(Frame f) throws IOException {
if (f.type == AMQP.FRAME_HEADER) {
this.contentHeader = AMQImpl.readContentHeaderFrom(f.getInputStream());
long bodySize = this.contentHeader.getBodySize();
if (bodySize >= this.maxBodyLength) {
throw new IllegalStateException(format(
"Message body is too large (%d), maximum configured size is %d. " +
"See ConnectionFactory#setMaxInboundMessageBodySize " +
"if you need to increase the limit.",
bodySize, this.maxBodyLength
));
}
this.remainingBodyBytes = bodySize;
updateContentBodyState();
} else {
throw new UnexpectedFrameError(f, AMQP.FRAME_HEADER);
}
}
private void consumeBodyFrame(Frame f) {
if (f.type == AMQP.FRAME_BODY) {
byte[] fragment = f.getPayload();
this.remainingBodyBytes -= fragment.length;
updateContentBodyState();
if (this.remainingBodyBytes < 0) {
throw new UnsupportedOperationException("%%%%%% FIXME unimplemented");
}
appendBodyFragment(fragment);
} else {
throw new UnexpectedFrameError(f, AMQP.FRAME_BODY);
}
}
/** Stitches together a fragmented content body into a single byte array */
private byte[] coalesceContentBody() {
if (this.bodyLength == 0) return EMPTY_BYTE_ARRAY;
if (this.bodyN.size() == 1) return this.bodyN.get(0);
byte[] body = new byte[bodyLength];
int offset = 0;
for (byte[] fragment : this.bodyN) {
System.arraycopy(fragment, 0, body, offset, fragment.length);
offset += fragment.length;
}
this.bodyN.clear();
this.bodyN.add(body);
return body;
}
public synchronized byte[] getContentBody() {
return coalesceContentBody();
}
private void appendBodyFragment(byte[] fragment) {
if (fragment == null || fragment.length == 0) return;
bodyN.add(fragment);
bodyLength += fragment.length;
}
/**
* @param f frame to be incorporated
* @return true if command becomes complete
* @throws IOException if error reading frame
*/
public synchronized boolean handleFrame(Frame f) throws IOException
{
switch (this.state) {
case EXPECTING_METHOD: consumeMethodFrame(f); break;
case EXPECTING_CONTENT_HEADER: consumeHeaderFrame(f); break;
case EXPECTING_CONTENT_BODY: consumeBodyFrame(f); break;
default:
throw new IllegalStateException("Bad Command State " + this.state);
}
return isComplete();
}
}