org.jgroups.Message Maven / Gradle / Ivy
package org.jgroups;
import org.jgroups.conf.ClassConfigurator;
import org.jgroups.util.*;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Map;
import java.util.function.Supplier;
/**
* A Message encapsulates data sent to members of a group. It contains among other things the
* address of the sender, the destination address, a payload (byte buffer) and a list of headers.
* Headers are added by protocols on the sender side and removed by protocols on the receiver's side.
*
* The byte buffer can point to a reference, and we can subset it using index and length. However,
* when the message is serialized, we only write the bytes between index and length.
*
* @since 2.0
* @author Bela Ban
*/
public class Message implements Streamable, Constructable {
protected Address dest;
protected Address sender;
/** The payload */
protected byte[] buf;
/** The index into the payload (usually 0) */
protected int offset;
/** The number of bytes in the buffer (usually buf.length is buf not equal to null). */
protected int length;
/** All headers are placed here */
protected volatile Header[] headers;
protected volatile short flags;
protected volatile byte transient_flags; // transient_flags is neither marshalled nor copied
static final byte DEST_SET = 1;
static final byte SRC_SET = 1 << 1;
static final byte BUF_SET = 1 << 2;
// =============================== Flags ====================================
public enum Flag {
OOB((short) 1), // message is out-of-band
DONT_BUNDLE( (short)(1 << 1)), // don't bundle message at the transport
NO_FC( (short)(1 << 2)), // bypass flow control
NO_RELIABILITY((short)(1 << 4)), // bypass UNICAST(2) and NAKACK
NO_TOTAL_ORDER((short)(1 << 5)), // bypass total order (e.g. SEQUENCER)
NO_RELAY( (short)(1 << 6)), // bypass relaying (RELAY)
RSVP( (short)(1 << 7)), // ack of a multicast (https://issues.jboss.org/browse/JGRP-1389)
RSVP_NB( (short)(1 << 8)), // non blocking RSVP
INTERNAL( (short)(1 << 9)), // for internal use by JGroups only, don't use !
SKIP_BARRIER( (short)(1 << 10)); // passing messages through a closed BARRIER
final short value;
Flag(short value) {this.value=value;}
public short value() {return value;}
}
// =========================== Transient flags ==============================
public enum TransientFlag {
OOB_DELIVERED( (short)(1)),
DONT_LOOPBACK( (short)(1 << 1)); // don't loop back up if this flag is set and it is a multicast message
final short value;
TransientFlag(short flag) {value=flag;}
public short value() {return value;}
}
/**
* Constructs a message given a destination address
* @param dest The Address of the receiver. If it is null, then the message is sent to the group. Otherwise, it is
* sent to a single member.
*/
public Message(Address dest) {
setDest(dest);
headers=createHeaders(Util.DEFAULT_HEADERS);
}
/**
* Constructs a message given a destination and source address and the payload byte buffer
* @param dest The Address of the receiver. If it is null, then the message is sent to the group. Otherwise, it is
* sent to a single member.
* @param buf The payload. Note that this buffer must not be modified (e.g. buf[0]='x' is not
* allowed) since we don't copy the contents.
*/
public Message(Address dest, byte[] buf) {
this(dest, buf, 0, buf != null? buf.length : 0);
}
/**
* Constructs a message. The index and length parameters provide a reference to a byte buffer, rather than a copy,
* and refer to a subset of the buffer. This is important when we want to avoid copying. When the message is
* serialized, only the subset is serialized.
*
* Note that the byte[] buffer passed as argument must not be modified. Reason: if we retransmit the
* message, it would still have a ref to the original byte[] buffer passed in as argument, and so we would
* retransmit a changed byte[] buffer !
*
*
* @param dest The Address of the receiver. If it is null, then the message is sent to the group. Otherwise, it is
* sent to a single member.
* @param buf A reference to a byte buffer
* @param offset The index into the byte buffer
* @param length The number of bytes to be used from buf. Both index and length are checked
* for array index violations and an ArrayIndexOutOfBoundsException will be thrown if invalid
*/
public Message(Address dest, byte[] buf, int offset, int length) {
this(dest);
setBuffer(buf, offset, length);
}
public Message(Address dest, Buffer buf) {
this(dest);
setBuffer(buf);
}
/**
* Constructs a message given a destination and source address and the payload object
* @param dest The Address of the receiver. If it is null, then the message is sent to the group. Otherwise, it is
* sent to a single member.
* @param obj The object that will be marshalled into the byte buffer. Has to be serializable (e.g. implementing
* Serializable, Externalizable or Streamable, or be a basic type (e.g. Integer, Short etc)).
*/
public Message(Address dest, Object obj) {
this(dest);
setObject(obj);
}
public Message() {
this(true);
}
public Message(boolean create_headers) {
if(create_headers)
headers=createHeaders(Util.DEFAULT_HEADERS);
}
public Supplier extends Message> create() {
return Message::new;
}
public Address getDest() {return dest;}
public Address dest() {return dest;}
public Message setDest(Address new_dest) {dest=new_dest; return this;}
public Message dest(Address new_dest) {dest=new_dest; return this;}
public Address getSrc() {return sender;}
public Address src() {return sender;}
public Message setSrc(Address new_src) {sender=new_src; return this;}
public Message src(Address new_src) {sender=new_src; return this;}
public int getOffset() {return offset;}
public int offset() {return offset;}
public int getLength() {return length;}
public int length() {return length;}
/**
* Returns a reference to the payload (byte buffer). Note that this buffer should not be
* modified as we do not copy the buffer on copy() or clone(): the buffer of the copied message
* is simply a reference to the old buffer.
* Even if offset and length are used: we return the entire buffer, not a subset.
*/
public byte[] getRawBuffer() {return buf;}
public byte[] rawBuffer() {return buf;}
public byte[] buffer() {return getBuffer();}
public Buffer buffer2() {return getBuffer2();}
public Message buffer(byte[] b) {return setBuffer(b);}
public Message buffer(Buffer b) {return setBuffer(b);}
public int getNumHeaders() {return Headers.size(this.headers);}
public int numHeaders() {return Headers.size(this.headers);}
/**
* Returns a copy of the buffer if offset and length are used, otherwise a reference.
* @return byte array with a copy of the buffer.
*/
public byte[] getBuffer() {
if(buf == null)
return null;
if(offset == 0 && length == buf.length)
return buf;
else {
byte[] retval=new byte[length];
System.arraycopy(buf, offset, retval, 0, length);
return retval;
}
}
public Buffer getBuffer2() {
if(buf == null)
return null;
return new Buffer(buf, offset, length);
}
/**
* Sets the buffer.
* Note that the byte[] buffer passed as argument must not be modified. Reason: if we retransmit the
* message, it would still have a ref to the original byte[] buffer passed in as argument, and so we would
* retransmit a changed byte[] buffer !
*/
public Message setBuffer(byte[] b) {
buf=b;
if(buf != null) {
offset=0;
length=buf.length;
}
else
offset=length=0;
return this;
}
/**
* Sets the internal buffer to point to a subset of a given buffer.
*
* Note that the byte[] buffer passed as argument must not be modified. Reason: if we retransmit the
* message, it would still have a ref to the original byte[] buffer passed in as argument, and so we would
* retransmit a changed byte[] buffer !
*
*
* @param b The reference to a given buffer. If null, we'll reset the buffer to null
* @param offset The initial position
* @param length The number of bytes
*/
public Message setBuffer(byte[] b, int offset, int length) {
buf=b;
if(buf != null) {
if(offset < 0 || offset > buf.length)
throw new ArrayIndexOutOfBoundsException(offset);
if((offset + length) > buf.length)
throw new ArrayIndexOutOfBoundsException((offset+length));
this.offset=offset;
this.length=length;
}
else
this.offset=this.length=0;
return this;
}
/**
* Sets the buffer
* Note that the byte[] buffer passed as argument must not be modified. Reason: if we retransmit the
* message, it would still have a ref to the original byte[] buffer passed in as argument, and so we would
* retransmit a changed byte[] buffer !
*/
public Message setBuffer(Buffer buf) {
if(buf != null) {
this.buf=buf.getBuf();
this.offset=buf.getOffset();
this.length=buf.getLength();
}
return this;
}
/**
* Returns a reference to the headers hashmap, which is immutable. Any attempt to modify
* the returned map will cause a runtime exception
*/
public Map getHeaders() {
return Headers.getHeaders(this.headers);
}
public String printHeaders() {
return Headers.printHeaders(this.headers);
}
/**
* Takes an object and uses Java serialization to generate the byte[] buffer which is set in the
* message. Parameter 'obj' has to be serializable (e.g. implementing Serializable,
* Externalizable or Streamable, or be a basic type (e.g. Integer, Short etc)).
*/
public Message setObject(Object obj) {
if(obj == null) return this;
if(obj instanceof byte[])
return setBuffer((byte[])obj);
if(obj instanceof Buffer)
return setBuffer((Buffer)obj);
try {
return setBuffer(Util.objectToByteBuffer(obj));
}
catch(Exception ex) {
throw new IllegalArgumentException(ex);
}
}
public T getObject() {
return getObject(null);
}
/**
* Uses custom serialization to create an object from the buffer of the message. Note that this is dangerous when
* using your own classloader, e.g. inside of an application server ! Most likely, JGroups will use the system
* classloader to deserialize the buffer into an object, whereas (for example) a web application will want to use
* the webapp's classloader, resulting in a ClassCastException. The recommended way is for the application to use
* their own serialization and only pass byte[] buffer to JGroups.
* As of 3.5, a classloader can be passed in. It will be used first to find a class, before contacting
* the other classloaders in the list. If null, the default list of classloaders will be used.
* @return the object
*/
public T getObject(ClassLoader loader) {
try {
return Util.objectFromByteBuffer(buf, offset, length, loader);
}
catch(Exception ex) {
throw new IllegalArgumentException(ex);
}
}
/**
* Sets a number of flags in a message
* @param flags The flag or flags
* @return A reference to the message
*/
public Message setFlag(Flag ... flags) {
if(flags != null) {
short tmp=this.flags;
for(Flag flag : flags) {
if(flag != null)
tmp|=flag.value();
}
this.flags=tmp;
}
return this;
}
/**
* Same as {@link #setFlag(Flag...)} except that transient flags are not marshalled
* @param flags The flag
*/
public Message setTransientFlag(TransientFlag ... flags) {
if(flags != null) {
short tmp=this.transient_flags;
for(TransientFlag flag : flags)
if(flag != null)
tmp|=flag.value();
this.transient_flags=(byte)tmp;
}
return this;
}
/**
* Sets the flags from a short. Not recommended (use {@link #setFlag(org.jgroups.Message.Flag...)} instead),
* as the internal representation of flags might change anytime.
* @param flag
* @return
*/
public Message setFlag(short flag) {
short tmp=this.flags;
tmp|=flag;
this.flags=tmp;
return this;
}
public Message setTransientFlag(short flag) {
short tmp=this.transient_flags;
tmp|=flag;
this.transient_flags=(byte)tmp;
return this;
}
/**
* Returns the internal representation of flags. Don't use this, as the internal format might change at any time !
* This is only used by unit test code
* @return
*/
public short getFlags() {return flags;}
public short getTransientFlags() {return transient_flags;}
/**
* Clears a number of flags in a message
* @param flags The flags
* @return A reference to the message
*/
public Message clearFlag(Flag ... flags) {
if(flags != null) {
short tmp=this.flags;
for(Flag flag : flags)
if(flag != null)
tmp&=~flag.value();
this.flags=tmp;
}
return this;
}
public Message clearTransientFlag(TransientFlag ... flags) {
if(flags != null) {
short tmp=this.transient_flags;
for(TransientFlag flag : flags)
if(flag != null)
tmp&=~flag.value();
this.transient_flags=(byte)tmp;
}
return this;
}
public static boolean isFlagSet(short flags, Flag flag) {
return flag != null && ((flags & flag.value()) == flag.value());
}
/**
* Checks if a given flag is set
* @param flag The flag
* @return Whether or not the flag is currently set
*/
public boolean isFlagSet(Flag flag) {
return isFlagSet(flags, flag);
}
public static boolean isTransientFlagSet(short flags, TransientFlag flag) {
return flag != null && (flags & flag.value()) == flag.value();
}
public boolean isTransientFlagSet(TransientFlag flag) {
return isTransientFlagSet(transient_flags, flag);
}
/**
* Atomically checks if a given flag is set and - if not - sets it. When multiple threads
* concurrently call this method with the same flag, only one of them will be able to set the
* flag
*
* @param flag
* @return True if the flag could be set, false if not (was already set)
*/
public synchronized boolean setTransientFlagIfAbsent(TransientFlag flag) {
if(isTransientFlagSet(flag))
return false;
setTransientFlag(flag);
return true;
}
/*---------------------- Used by protocol layers ----------------------*/
/** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */
public Message putHeader(short id, Header hdr) {
if(id < 0)
throw new IllegalArgumentException("An ID of " + id + " is invalid");
if(hdr != null)
hdr.setProtId(id);
synchronized(this) {
Header[] resized_array=Headers.putHeader(this.headers, id, hdr, true);
if(resized_array != null)
this.headers=resized_array;
}
return this;
}
public T getHeader(short id) {
if(id <= 0)
throw new IllegalArgumentException("An ID of " + id + " is invalid. Add the protocol which calls " +
"getHeader() to jg-protocol-ids.xml");
return Headers.getHeader(this.headers, id);
}
/** Returns a header for a range of IDs, or null if not found */
public T getHeader(short ... ids) {
if(ids == null || ids.length == 0)
return null;
return Headers.getHeader(this.headers, ids);
}
/*---------------------------------------------------------------------*/
public Message copy() {
return copy(true);
}
/**
* Create a copy of the message. If offset and length are used (to refer to another buffer), the
* copy will contain only the subset offset and length point to, copying the subset into the new
* copy.
*
* @param copy_buffer
* @return Message with specified data
*/
public Message copy(boolean copy_buffer) {
return copy(copy_buffer, true);
}
/**
* Create a copy of the message. If offset and length are used (to refer to another buffer), the
* copy will contain only the subset offset and length point to, copying the subset into the new
* copy.
* Note that for headers, only the arrays holding references to the headers are copied, not the headers themselves !
* The consequence is that the headers array of the copy hold the *same* references as the original, so do *not*
* modify the headers ! If you want to change a header, copy it and call {@link Message#putHeader(short,Header)} again.
*
* @param copy_buffer
* @param copy_headers
* Copy the headers
* @return Message with specified data
*/
public Message copy(boolean copy_buffer, boolean copy_headers) {
Message retval=new Message(false);
retval.dest=dest;
retval.sender=sender;
short tmp_flags=this.flags;
byte tmp_tflags=this.transient_flags;
retval.flags=tmp_flags;
retval.transient_flags=tmp_tflags;
if(copy_buffer && buf != null)
retval.setBuffer(buf, offset, length);
retval.headers=copy_headers && headers != null? Headers.copy(this.headers) : createHeaders(Util.DEFAULT_HEADERS);
return retval;
}
/**
* Doesn't copy any headers except for those with ID >= copy_headers_above
*
* @param copy_buffer
* @param starting_id
* @return A message with headers whose ID are >= starting_id
*/
public Message copy(boolean copy_buffer, short starting_id) {
return copy(copy_buffer, starting_id, (short[])null);
}
/**
* Copies a message. Copies only headers with IDs >= starting_id or IDs which are in the copy_only_ids list
* @param copy_buffer
* @param starting_id
* @param copy_only_ids
* @return
*/
public Message copy(boolean copy_buffer, short starting_id, short ... copy_only_ids) {
Message retval=copy(copy_buffer, false);
for(Map.Entry entry: getHeaders().entrySet()) {
short id=entry.getKey();
if(id >= starting_id || Util.containsId(id, copy_only_ids))
retval.putHeader(id, entry.getValue());
}
return retval;
}
public Message makeReply() {
Message retval=new Message(sender);
if(dest != null)
retval.setSrc(dest);
return retval;
}
public String toString() {
return String.format("[%s to %s, %d bytes%s%s]",
sender,
dest == null? "" : dest,
length,
flags > 0? ", flags=" + flagsToString(flags) : "",
transient_flags > 0? ", transient_flags=" + transientFlagsToString(transient_flags) : "");
}
public String printObjectHeaders() {
return Headers.printObjectHeaders(this.headers);
}
/* ----------------------------------- Interface Streamable ------------------------------- */
/** Writes the message to the output stream */
@Override public void writeTo(DataOutput out) throws IOException {
byte leading=0;
if(dest != null)
leading=Util.setFlag(leading, DEST_SET);
if(sender != null)
leading=Util.setFlag(leading, SRC_SET);
if(buf != null)
leading=Util.setFlag(leading, BUF_SET);
// 1. write the leading byte first
out.write(leading);
// 2. the flags (e.g. OOB, LOW_PRIO), skip the transient flags
out.writeShort(flags);
// 3. dest_addr
if(dest != null)
Util.writeAddress(dest, out);
// 4. src_addr
if(sender != null)
Util.writeAddress(sender, out);
// 5. headers
Header[] hdrs=this.headers;
int size=Headers.size(hdrs);
out.writeShort(size);
if(size > 0) {
for(Header hdr : hdrs) {
if(hdr == null)
break;
out.writeShort(hdr.getProtId());
writeHeader(hdr, out);
}
}
// 6. buf
if(buf != null) {
out.writeInt(length);
out.write(buf, offset, length);
}
}
/**
* Writes the message to the output stream, but excludes the dest and src addresses unless the
* src address given as argument is different from the message's src address
* @param excluded_headers Don't marshal headers that are part of excluded_headers
*/
public void writeToNoAddrs(Address src, DataOutput out, short ... excluded_headers) throws IOException {
byte leading=0;
boolean write_src_addr=src == null || sender != null && !sender.equals(src);
if(write_src_addr)
leading=Util.setFlag(leading, SRC_SET);
if(buf != null)
leading=Util.setFlag(leading, BUF_SET);
// 1. write the leading byte first
out.write(leading);
// 2. the flags (e.g. OOB, LOW_PRIO)
out.writeShort(flags);
// 4. src_addr
if(write_src_addr)
Util.writeAddress(sender, out);
// 5. headers
Header[] hdrs=this.headers;
int size=Headers.size(hdrs, excluded_headers);
out.writeShort(size);
if(size > 0) {
for(Header hdr : hdrs) {
if(hdr == null)
break;
short id=hdr.getProtId();
if(Util.containsId(id, excluded_headers))
continue;
out.writeShort(id);
writeHeader(hdr, out);
}
}
// 6. buf
if(buf != null) {
out.writeInt(length);
out.write(buf, offset, length);
}
}
/** Reads the message's contents from an input stream */
@Override public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
// 1. read the leading byte first
byte leading=in.readByte();
// 2. the flags
flags=in.readShort();
// 3. dest_addr
if(Util.isFlagSet(leading, DEST_SET))
dest=Util.readAddress(in);
// 4. src_addr
if(Util.isFlagSet(leading, SRC_SET))
sender=Util.readAddress(in);
// 5. headers
int len=in.readShort();
this.headers=createHeaders(len);
for(int i=0; i < len; i++) {
short id=in.readShort();
Header hdr=readHeader(in).setProtId(id);
this.headers[i]=hdr;
}
// 6. buf
if(Util.isFlagSet(leading, BUF_SET)) {
len=in.readInt();
buf=new byte[len];
in.readFully(buf, 0, len);
length=len;
}
}
/** Reads the message's contents from an input stream, but skips the buffer and instead returns the
* position (offset) at which the buffer starts */
public int readFromSkipPayload(ByteArrayDataInputStream in) throws IOException, ClassNotFoundException {
// 1. read the leading byte first
byte leading=in.readByte();
// 2. the flags
flags=in.readShort();
// 3. dest_addr
if(Util.isFlagSet(leading, DEST_SET))
dest=Util.readAddress(in);
// 4. src_addr
if(Util.isFlagSet(leading, SRC_SET))
sender=Util.readAddress(in);
// 5. headers
int len=in.readShort();
headers=createHeaders(len);
for(int i=0; i < len; i++) {
short id=in.readShort();
Header hdr=readHeader(in).setProtId(id);
this.headers[i]=hdr;
}
// 6. buf
if(!Util.isFlagSet(leading, BUF_SET))
return -1;
length=in.readInt();
return in.position();
}
/* --------------------------------- End of Interface Streamable ----------------------------- */
/**
* Returns the exact size of the marshalled message. Uses method size() of each header to compute
* the size, so if a Header subclass doesn't implement size() we will use an approximation.
* However, most relevant header subclasses have size() implemented correctly. (See
* org.jgroups.tests.SizeTest).
* The return type is a long as this is the length of the payload ({@link #getLength()}) plus metadata (e.g. flags,
* headers, source and dest addresses etc). Since the largest payload can be Integer.MAX_VALUE, adding the metadata
* might lead to an int overflow, that's why we use a long.
* @return The number of bytes for the marshalled message
*/
public long size() {
long retval=(long)Global.BYTE_SIZE // leading byte
+ Global.SHORT_SIZE; // flags
if(dest != null)
retval+=Util.size(dest);
if(sender != null)
retval+=Util.size(sender);
retval+=Global.SHORT_SIZE; // number of headers
retval+=Headers.marshalledSize(this.headers);
if(buf != null)
retval+=Global.INT_SIZE // length (integer)
+ length; // number of bytes in the buffer
return retval;
}
/* ----------------------------------- Private methods ------------------------------- */
public static String flagsToString(short flags) {
StringBuilder sb=new StringBuilder();
boolean first=true;
Flag[] all_flags=Flag.values();
for(Flag flag: all_flags) {
if(isFlagSet(flags, flag)) {
if(first)
first=false;
else
sb.append("|");
sb.append(flag);
}
}
return sb.toString();
}
public static String transientFlagsToString(short flags) {
StringBuilder sb=new StringBuilder();
boolean first=true;
TransientFlag[] all_flags=TransientFlag.values();
for(TransientFlag flag: all_flags) {
if(isTransientFlagSet(flags, flag)) {
if(first)
first=false;
else
sb.append("|");
sb.append(flag);
}
}
return sb.toString();
}
protected static void writeHeader(Header hdr, DataOutput out) throws IOException {
short magic_number=hdr.getMagicId();
out.writeShort(magic_number);
hdr.writeTo(out);
}
protected static Header readHeader(DataInput in) throws IOException, ClassNotFoundException {
short magic_number=in.readShort();
Header hdr=ClassConfigurator.create(magic_number);
hdr.readFrom(in);
return hdr;
}
protected static Header[] createHeaders(int size) {
return size > 0? new Header[size] : new Header[3];
}
/* ------------------------------- End of Private methods ---------------------------- */
}