package dmg.cells.nucleus ;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkState;
/**
* Do not subclass - otherwise raw encoding in LocationMgrTunnel will break.
*
* @author Patrick Fuhrmann
* @version 0.1, 15 Feb 1998
*/
public final class CellMessage implements Cloneable , Serializable {
private static final long serialVersionUID = -5559658187264201731L;
/**
* Maximum TTL adjustment in milliseconds.
*/
private static final int TTL_BUFFER_MAXIMUM = 10000;
/**
* Maximum TTL adjustment as a fraction of TTL.
*/
private static final float TTL_BUFFER_FRACTION = 0.10f;
private CellPath _source , _destination ;
private Object _message ;
private long _creationTime ;
private long _ttl = Long.MAX_VALUE;
private int _mode ;
private UOID _umid , _lastUmid ;
private byte[] _messageStream;
private boolean _isPersistent;
private Object _session;
private static final int ORIGINAL_MODE = 0 ;
private static final int STREAM_MODE = 1 ;
private transient long _receivedAt;
public CellMessage(CellAddressCore address, Serializable msg)
{
this(new CellPath(address));
_message = msg;
}
public CellMessage(CellAddressCore address)
{
this(new CellPath(address));
}
public CellMessage(CellPath path, Serializable msg)
{
this(path.clone());
_message = msg;
}
public CellMessage(CellPath path)
{
_source = new CellPath();
_destination = path;
_creationTime = System.currentTimeMillis();
_receivedAt = _creationTime;
_mode = ORIGINAL_MODE;
_umid = new UOID();
_lastUmid = _umid;
_session = CDC.getSession();
}
public CellMessage()
{
this(new CellPath());
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder() ;
sb.append( "<CM: S=" ).append(_source).
append( ";D=").append(_destination) ;
if( _mode == ORIGINAL_MODE ) {
sb.append(";C=").
append(_message.getClass().getName());
} else {
sb.append(";C=Stream");
}
sb.append( ";O=" ).append( _umid ).append( ";LO=" ).append( _lastUmid );
if (_session != null) {
sb.append(";SID=").append(_session);
}
if (_ttl < Long.MAX_VALUE) {
sb.append(";TTL=").append(_ttl);
}
sb.append('>') ;
return sb.toString() ;
}
@Override
public int hashCode(){ return _umid.hashCode() ; }
@Override
public boolean equals( Object obj ){
if( obj instanceof CellMessage ) {
return ((CellMessage) obj)._umid.equals(_umid);
} else if( obj instanceof UOID ) {
return obj.equals(_umid);
}
return false ;
}
public boolean isReply() { return _isPersistent; }
public UOID getUOID() { return _umid ; }
public UOID getLastUOID() { return _lastUmid ; }
public void setUOID( UOID umid ) {
_umid = umid ;
}
public void setLastUOID( UOID lastUOID ) {
_lastUmid = lastUOID ;
}
public Serializable getSession() { return (Serializable) _session; }
public void setSession(Serializable session) { _session = session; }
public void setTtl(long ttl) { _ttl = ttl; _receivedAt = System.currentTimeMillis(); }
public long getTtl() { return _ttl; }
public CellAddressCore getSourceAddress() { return _source.getSourceAddress(); }
public CellPath getDestinationPath(){ return _destination ; }
public CellPath getSourcePath(){ return _source ; }
public Serializable getMessageObject(){ return (Serializable) _message ; }
public void setMessageObject(Serializable obj)
{
checkState(_mode == ORIGINAL_MODE);
_message = obj;
}
public void revertDirection(){
checkState(!_source.getSourceAddress().isDomainAddress(), "Cannot return envelope to a domain address.");
_destination = _source.revert();
_source = new CellPath() ;
_lastUmid = _umid ;
_umid = new UOID();
_isPersistent = true;
}
public boolean isFinalDestination(){ return _destination.isFinalDestination() ; }
public boolean isFirstDestination(){ return _destination.isFirstDestination() ; }
public boolean nextDestination(){ return _destination.next() ; }
//
// package methods
//
boolean isStreamMode(){ return _mode == STREAM_MODE ; }
/**
* The method does not copy the message object - only the encoded message
* stream (if any).
*/
@Override
public CellMessage clone()
{
try {
CellMessage copy = (CellMessage) super.clone();
copy._destination = _destination.clone();
if (_source != null) {
copy._source = _source.clone();
}
copy._messageStream = _messageStream;
return copy;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public CellMessage encode() throws SerializationException
{
checkState(_mode == ORIGINAL_MODE);
CellMessage encoded = clone();
encoded._mode = STREAM_MODE;
encoded._message = null;
encoded._messageStream = encode(_message);
return encoded;
}
public CellMessage decode() throws SerializationException
{
checkState(_mode == STREAM_MODE);
CellMessage decoded = clone();
decoded._mode = ORIGINAL_MODE;
decoded._messageStream = null;
decoded._message = decode(_messageStream);
return decoded;
}
protected static byte[] encode(Object message)
{
int initialBufferSize = 256;
ByteArrayOutputStream array = new ByteArrayOutputStream(initialBufferSize);
try (ObjectOutputStream out = new ObjectOutputStream(array)) {
out.writeObject(message);
} catch (InvalidClassException e) {
throw new SerializationException("Failed to serialize object: "
+ e + "(this is usually a bug)", e);
} catch (NotSerializableException e) {
throw new SerializationException("Failed to serialize object because the object is not serializable (this is usually a bug)", e);
} catch (IOException e) {
throw new SerializationException("Failed to serialize object: " + e, e);
}
return array.toByteArray();
}
protected static Object decode(byte[] messageStream)
{
try (ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(messageStream))) {
return stream.readObject();
} catch (ClassNotFoundException e) {
throw new SerializationException("Failed to deserialize object: The class could not be found. Is there a software version mismatch in your installation?", e);
} catch (IOException e) {
throw new SerializationException("Failed to deserialize object: " + e, e);
}
}
public void addSourceAddress( CellAddressCore source ){
_source.add(source) ;
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
_receivedAt = System.currentTimeMillis();
}
/**
* Returns the number of milliseconds since this message was
* received by the local domain. If the message created in the
* local domain, then the method returns the number of
* milliseconds since it was created.
*/
public long getLocalAge()
{
return System.currentTimeMillis() - _receivedAt;
}
/**
* Returns the adjusted TTL of a message. The adjusted TTL is the
* TTL with some time subtracted to allow for cell communication
* overhead. Returns Long.MAX_VALUE if the TTL is infinite.
*/
public long getAdjustedTtl()
{
return (_ttl == Long.MAX_VALUE)
? Long.MAX_VALUE
: _ttl - Math.min(TTL_BUFFER_MAXIMUM, (long) (_ttl * TTL_BUFFER_FRACTION));
}
/**
* Writes CellMessage to a data output stream.
*
* The CellMessage must be in stream mode.
*
* This is the raw encoding used by tunnels since release 3.0.
*/
public void writeTo(DataOutput out) throws IOException
{
checkState(_mode == STREAM_MODE);
out.writeByte(_mode);
out.writeBoolean(_isPersistent);
out.writeLong(_creationTime);
out.writeLong(_ttl);
_umid.writeTo(out);
_lastUmid.writeTo(out);
_source.writeTo(out);
_destination.writeTo(out);
out.writeUTF(Objects.toString(_session, ""));
out.writeInt(_messageStream.length);
out.write(_messageStream);
}
/**
* Reads CellMessage from a data input stream.
*
* This is the raw encoding used by tunnels since release 3.0.
*/
public static CellMessage createFrom(DataInput in) throws IOException
{
CellMessage message = new CellMessage();
message._mode = in.readByte();
if (message._mode != STREAM_MODE) {
throw new IOException("Invalid message tunnel wire format.");
}
/* Need to initialize the transient reception time after the first field is read as
* this function may have been called while the input stream is empty.
*/
message._receivedAt = System.currentTimeMillis();
message._isPersistent = in.readBoolean();
message._creationTime = in.readLong();
message._ttl = in.readLong();
message._umid = UOID.createFrom(in);
message._lastUmid = UOID.createFrom(in);
message._source = CellPath.createFrom(in);
message._destination = CellPath.createFrom(in);
message._session = Strings.emptyToNull(in.readUTF());
int len = in.readInt();
message._messageStream = new byte[len];
in.readFully(message._messageStream);
return message;
}
}