
net.sf.eBus.messages.EMessage Maven / Gradle / Ivy
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2001 - 2011, 2013, 2016. Charles W. Rapp.
// All Rights Reserved.
//
package net.sf.eBus.messages;
import java.io.Serializable;
/**
* {@code EMessage} is the base class for all eBus messages and
* contains the message subject and timestamp.
* Application messages do not extend this class
* directly but one of the subclasses:
* {@link ENotificationMessage}, {@link ERequestMessage}, or
* {@link EReplyMessage}.
*
* Note: messages are limited to 31 fields. This
* is due to eBus binary serialization format. This number is
* decreased to 29 fields for sub-classes because
* {@code EMessage} used two fields: {@link #subject} and
* {@link #timestamp}. The work-around to this limitation is to
* group fields into an {@link EField} subclass, which is allowed
* to have 31 fields of its own. So if a message class uses two
* {@code EField} subclasses, it may contain up to 62 fields with
* 31 in each field subclass.
*
*
* Note: a {@code EMessage} subclass may
* not be used as a message field itself. That means
* that a message may not contain another message as a field.
* Again, the work-around is to group fields which are common to
* multiple {@code EMessage} subclasses in an {@code EField} and
* use that field subclass in the message subclasses.
*
*
* This class is immutable.
*
* De-serialization Constructor
* All {@code EMessage} and {@code EField} subclasses are
* required to provide a {@code public}
* constructor used to instantiate a de-serialized message. An
* example is provided as way of explanation. Consider a
* user-defined message class {@code OrderReply} which extends
* {@link EReplyMessage} and with the following fields:
*
* public final OrderPart[] parts;
* public final BigDecimal shippingCost;
* public final BigDecimal totalPrice;
*
* {@code EFieldInfo} defines the field order as
* {@code &at;fields={parts, shippingCost, totalPrice}}. Given this
* information, {@code OrderReply} must define the following
* constructor:
*
* public OrderReply(final String subject,
* final long timestamp,
* final EReplyMessage.ReplyStatus status,
* final String reason,
* OrderPart[] parts,
* BigDecimal shipping,
* BigDecimal totalPx)
* {
* super (subject, timestamp, status, reason);
*
* this.parts = parts;
* this.shippingCost = shipping;
* this.totalPrice = totalPx;
*
* // Other user code goes here if needed.
* }
*
* Because {@code OrderReply} is a reply message, {@code subject}
* and {@code timestamp} are inherited from {@code EMessage} and
* {@code status} and {@code reason} are inherited from
* {@code EReplyMessage}. Therefore, these fields must also be
* included in the constructor.
* Message Compilation
* Note: as of eBus 4.4.0, messages are
* automatically compiled when first used for serialization
* or de-serialization. This increases the time it takes to
* send or receive a message the first time. When a message
* is frequently transmitted, it is recommended that the
* message class is "compiled" by calling
* {@link net.sf.eBus.messages.type.DataType#findType(java.lang.Class)}
* for that message class. So when a message is sent or received
* the first time, the message compilation is already done.
*
* Message compilation also compiles all {@code EField} classes
* contained in the message class. Compilation cascades down to
* leaf fields which are pre-defined eBus field types.
*
*
* @see EMessageHeader
* @see EMessageObject
* @see ENotificationMessage
* @see ERequestMessage
* @see EReplyMessage
* @see EField
*
* @author Charles Rapp
*/
@EFieldInfo (fields = {"subject", "timestamp"})
public abstract class EMessage
extends EMessageObject
implements Serializable
{
//---------------------------------------------------------------
// Enums.
//
/**
* Messages are divided into four types: notification,
* request, reply and system. System messages are reserved
* for use by the eBus system itself and may not be used
* by applications.
*/
public enum MessageType
{
/**
* A notification is an event with an associated
* subject. Notifications are routed by subject.
*/
NOTIFICATION (ENotificationMessage.class),
/**
* A request contains a subject and request identifier
* and is routed by subject.
*/
REQUEST (ERequestMessage.class),
/**
* A reply contains a subject, request identifier,
* reply status and optional reason. Replies are routed
* by their destination address. The destination is
* the corresponding request source address.
*/
REPLY (EReplyMessage.class),
/**
* eBus system message. Reserved for use by the eBus
* system and may not be used by applications.
*/
SYSTEM (ESystemMessage.class);
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private MessageType(final Class extends EMessage> mc)
{
mMsgClass = mc;
} // end of MessageType(Class)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the associated message class.
* @return message class.
*/
public Class extends EMessage> messageClass()
{
return (mMsgClass);
} // end of messageClass()
/**
* Returns {@code true} if {@code mc} is not {@code null}
* and is assignable from this message type.
* @param mc check if this message class matches this
* type.
* @return {@code true} if {@code mc} matches this
* message type.
*/
public boolean isMatching(final Class mc)
{
return (mc != null && mMsgClass.isAssignableFrom(mc));
} // end of isMatching(Class)
//
// end of Get Methods.
//-------------------------------------------------------
//-----------------------------------------------------------
// Member data.
//
/**
* Message class associated with this message type.
*/
private final Class extends EMessage> mMsgClass;
} // end of enum MessageType
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Serialization version identifier.
*/
private static final long serialVersionUID = 0x040400L;
//-----------------------------------------------------------
// Locals.
//
/**
* The required message subject. The subject and the message
* class are used to route this message to the intended
* eBus clients.
*/
public final String subject;
/**
* The message timestamp in Java millisecond epoch time.
*/
public final long timestamp;
/**
* The message type. Either notification, request, reply, or
* system.
*/
private final MessageType mMessageType;
/**
* The message key combining the message class and subject.
*/
private final EMessageKey mKey;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new eBus message for the given subject and
* timestamp. The message type defaults to
* {@link MessageType#SYSTEM}.
*
* This constructor is required to be {@code public} by
* {@link net.sf.eBus.messages.type.MessageType#load}.
* @param subject the message timestamp.
* @param timestamp the message timestamp in Java,
* millisecond epoch time.
* @throws IllegalArgumentException
* if {@code subject} is either {@code null} or empty.
* @throws InvalidMessageException
* if this message violates the eBus correct message rules.
*/
public EMessage(final String subject,
final long timestamp)
throws IllegalArgumentException,
InvalidMessageException
{
this (subject, timestamp, MessageType.SYSTEM);
} // end of EMessage(String, long)
/**
* Creates a new eBus message based on the given subject,
* timestamp, and message type.
* @param subject the message subject.
* @param timestamp the message timestamp in Java,
* millisecond epoch time.
* @param msgType the message type.
* @throws IllegalArgumentException
* if:
*
* -
* {@code subject} is either of {@code null} or empty;
*
* -
* {@code msgType} is {@code null}.
*
*
* @throws InvalidMessageException
* if this message violates the eBus correct message rules.
*/
/* package */ EMessage(final String subject,
final long timestamp,
final MessageType msgType)
throws IllegalArgumentException,
InvalidMessageException
{
if (subject == null || subject.isEmpty() == true)
{
throw (
new IllegalArgumentException(
"null or empty subject"));
}
else if (msgType == null)
{
throw (new IllegalArgumentException("null msgType"));
}
this.subject = subject;
this.timestamp = timestamp;
this.mMessageType = msgType;
this.mKey = new EMessageKey(this.getClass(), subject);
} // end of EMessage(String, long, MessageType)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code o} is a
* non-{@code null EMessage} instance with a subject and
* timestamp equal to {@code this EMessage} instance and
* {@code false} otherwise.
* @param o comparison object.
* @return {@code true} if the message fields are equal
* and {@code false} otherwise.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (retcode == false && o instanceof EMessage)
{
final EMessage msg = (EMessage) o;
retcode = (subject.equals(msg.subject) == true &&
timestamp == msg.timestamp);
}
return (retcode);
} // end of equals(Object)
/**
* Returns the message header hash code.
* @return the message header hash code.
*/
@Override
public int hashCode()
{
return ((subject.hashCode() * 37) +
(Long.valueOf(timestamp)).hashCode());
} // end of hashCode()
/**
* Returns the message subject and timestamp as a string.
* @return the message as text.
*/
@Override
public String toString()
{
return (
String.format(
"%1$s:%2$s%n timestamp: %3$tY/%3$tm/%3$td %3$tH:%3$tM:%3$tS.%3$tL",
this.getClass(),
subject,
new java.util.Date(timestamp)));
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get methods.
//
/**
* Returns the unique message key based on the message class
* and subject.
* @return the unique message key based on the message class
* and subject.
*/
public EMessageKey key()
{
return (mKey);
} // end of key()
/**
* Returns the
* {@link net.sf.eBus.messages.EMessage.MessageType message type}.
* @return the message type.
*/
public MessageType messageType()
{
return (mMessageType);
} // end of messageType()
/**
* Returns {@code true} if this is a system message and
* {@code false} otherwise. Only eBus is allowed to transmit
* a system message.
* @return {@code true} if this is a system message and
* {@code false} otherwise.
*/
public boolean isSystemMessage()
{
return (mMessageType == MessageType.SYSTEM);
} // end of isSystemMessage()
/**
* Returns {@code true} if this is an application
* message and {@code false} otherwise.
* @return {@code true} if this is an application
* message and {@code false} otherwise.
*/
public boolean isApplicationMessage()
{
return (mMessageType != MessageType.SYSTEM);
} // end of isApplicationMessage()
//
// end of Get methods.
//-----------------------------------------------------------
} // end of class EMessage