/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.network.protocol;
import alluxio.exception.status.AlluxioStatusException;
import alluxio.exception.status.Status;
import alluxio.network.protocol.databuffer.DataBuffer;
import alluxio.network.protocol.databuffer.DataFileChannel;
import alluxio.network.protocol.databuffer.DataNettyBufferV2;
import alluxio.proto.dataserver.Protocol;
import alluxio.proto.dataserver.Protocol.Response;
import alluxio.util.proto.ProtoMessage;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;
import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import javax.annotation.concurrent.ThreadSafe;
/**
* This is the main base class for all protocol buffer based RPC messages to the DataServer.
* Each RPC message is composed of a proto message and a data buffer.
*
* Encoded format:
* [proto message length][serialized proto message][data buffer]
* The proto message length doesn't include the length of itself (the length field).
*
* Note: The data buffer must be released when it is not used. Usually this is how it is released:
* 1. On the consumer side, a {@link RPCProtoMessage} is decoded and the data buffer is extracted.
* The ownership of the data buffer is transferred from then on.
* 2. On the producer side, a {@link RPCProtoMessage} is created. It will be sent on the wire via
* netty which will take ownership of the data buffer.
* Given the above usage patterns, {@link RPCProtoMessage} doesn't provide a 'release' interface
* to avoid confusing the user.
*/
@ThreadSafe
public final class RPCProtoMessage extends RPCMessage {
private final ProtoMessage mMessage;
private final byte[] mMessageEncoded;
private final DataBuffer mData;
/**
* Creates an instance of {@link RPCProtoMessage}.
*
* @param message the message
* @param data the data which can be null. Ownership is taken by this class
*/
public RPCProtoMessage(ProtoMessage message, DataBuffer data) {
if (data != null) {
Preconditions
.checkArgument((data instanceof DataNettyBufferV2) || (data instanceof DataFileChannel),
"Only DataNettyBufferV2 and DataFileChannel are allowed.");
}
mMessage = message;
mMessageEncoded = message.toByteArray();
if (data != null && data.getLength() > 0) {
mData = data;
} else if (data != null) {
data.release();
mData = null;
} else {
mData = null;
}
}
/**
* Creates an instance of {@link RPCProtoMessage} without data part.
*
* @param message the message
*/
public RPCProtoMessage(ProtoMessage message) {
this(message, null);
}
/**
* Creates an instance of {@link RPCProtoMessage} from a serialized proto message.
*
* @param serialized the serialized message
* @param prototype the prototype of the message used to identify the type of the message
* @param data the data which can be null
*/
public RPCProtoMessage(byte[] serialized, ProtoMessage prototype, DataBuffer data) {
Preconditions
.checkArgument((data instanceof DataNettyBufferV2) || (data instanceof DataFileChannel),
"Only DataNettyBufferV2 and DataFileChannel are allowed.");
mMessage = ProtoMessage.parseFrom(serialized, prototype);
mMessageEncoded = Arrays.copyOf(serialized, serialized.length);
if (data != null && data.getLength() > 0) {
mData = data;
} else if (data != null) {
data.release();
mData = null;
} else {
mData = null;
}
}
@Override
public int getEncodedLength() {
return Ints.BYTES + mMessageEncoded.length;
}
@Override
public void encode(ByteBuf out) {
out.writeInt(mMessageEncoded.length);
out.writeBytes(mMessageEncoded);
}
/**
* Decodes the message from a buffer. This method increments the refcount of the bytebuf passed
* by 1.
*
* @param in the buffer
* @param prototype a message prototype used to infer the type of the message
* @return the message decoded
*/
public static RPCProtoMessage decode(ByteBuf in, ProtoMessage prototype) {
int length = in.readInt();
byte[] serialized = new byte[length];
in.readBytes(serialized);
in.retain();
return new RPCProtoMessage(serialized, prototype, new DataNettyBufferV2(in));
}
/**
* Throws the exception represented by this {@link RPCProtoMessage} if there is one.
*/
public void unwrapException() throws AlluxioStatusException {
Response response = getMessage().asResponse();
Status status = Status.fromProto(response.getStatus());
if (status != Status.OK) {
throw AlluxioStatusException.from(status, response.getMessage());
}
}
@Override
public Type getType() {
if (mMessage.isReadRequest()) {
return RPCMessage.Type.RPC_READ_REQUEST;
} else if (mMessage.isWriteRequest()) {
return RPCMessage.Type.RPC_WRITE_REQUEST;
} else if (mMessage.isResponse()) {
return RPCMessage.Type.RPC_RESPONSE;
} else if (mMessage.isHeartbeat()) {
return RPCMessage.Type.RPC_HEARTBEAT;
} else {
return RPCMessage.Type.RPC_UNKNOWN;
}
}
@Override
public void validate() {
}
@Override
public boolean hasPayload() {
return getPayloadDataBuffer() != null;
}
@Override
public DataBuffer getPayloadDataBuffer() {
return mData;
}
/**
* @return the message
*/
public ProtoMessage getMessage() {
return mMessage;
}
/**
* Creates a response for a given {@link AlluxioStatusException}.
*
* @param se the {@link AlluxioStatusException}
* @return the created {@link RPCProtoMessage}
*/
public static RPCProtoMessage createResponse(AlluxioStatusException se) {
String message = se.getMessage() != null ? se.getMessage() : "";
return createResponse(se.getStatus(), message, null);
}
/**
* Creates a response for a given status, message, and data buffer.
*
* @param status the status code
* @param message the message
* @param data the data buffer
* @return the created {@link RPCProtoMessage}
*/
public static RPCProtoMessage createResponse(Status status, String message, DataBuffer data) {
Response response = Protocol.Response.newBuilder().setStatus(Status.toProto(status))
.setMessage(message).build();
return new RPCProtoMessage(new ProtoMessage(response), data);
}
/**
* Creates an OK response with data.
*
* @param data the data
* @return the message created
*/
public static RPCProtoMessage createOkResponse(DataBuffer data) {
return createResponse(Status.OK, "", data);
}
/**
* Creates a response in CANCELLED state.
*
* @return the message created
*/
public static RPCProtoMessage createCancelResponse() {
return createResponse(Status.CANCELED, "canceled", null);
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("message", mMessage)
.add("dataLength", mData == null ? 0 : mData.getLength()).toString();
}
}