net.sf.eBus.messages.EMessage Maven / Gradle / Ivy
//
// Copyright 2001 - 2011, 2013, 2016, 2019 Charles W. Rapp
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package net.sf.eBus.messages;
import com.google.common.base.Strings;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
import net.sf.eBus.util.Validator;
/**
* {@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
* be used as a message field itself. That means
* that a message may contain another message as a field.
*
*
* This class is immutable.
*
*
* See {@link EMessageObject} for detailed explanation of the
* required builder inner class.
*
*
* @see EMessageHeader
* @see EMessageObject
* @see ENotificationMessage
* @see ERequestMessage
* @see EReplyMessage
* @see EField
*
* @author Charles Rapp
*/
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.
//
/**
* eBus subjects are restricted to {@value} characters.
* eBus messages with subjects exceeding this limit cannot
* be created or de-serialized.
*/
public static final int MAX_SUBJECT_LENGTH = 512;
/**
* Serialization version identifier.
*/
private static final long serialVersionUID = 0x050200L;
//-----------------------------------------------------------
// 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 based on the given message
* builder. {@code builder} is guaranteed to contain a valid
* message configuration at this point.
* @param builder contains the eBus message configuration.
*/
protected EMessage(final Builder, ?> builder)
{
this.subject = builder.mSubject;
this.timestamp = builder.mTimestamp;
this.mMessageType = builder.mType;
this.mKey = new EMessageKey(this.getClass(), this.subject);
} // end of EMessage(Builder)
//
// 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 && o instanceof EMessage)
{
final EMessage msg = (EMessage) o;
retcode = (subject.equals(msg.subject) &&
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 (Objects.hash(subject, timestamp));
} // end of hashCode()
/**
* Returns 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()
/**
* Returns message timestamp as an {@code Instant} rather
* than as an epoch millisecond {@code long}.
* @return message timestamp as {@code Instant}.
*/
public Instant timestampAsInstant()
{
return (Instant.ofEpochMilli(timestamp));
} // end of timestampAsInstant()
//
// end of Get methods.
//-----------------------------------------------------------
//---------------------------------------------------------------
// Inner classes.
//
/**
* Base class for all {@link EMessage} builders. Used by
* eBus when de-serializing an encoded message back into the
* target message object.
*
* @param builds this target message class.
* @param message builder subclass. Needed to return the
* correct builder type when setting fields. If this were not
* the case, field chaining would not work.
*/
@SuppressWarnings ("unchecked")
public abstract static class Builder>
extends EMessageObject.Builder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Message subject.
*/
private String mSubject;
/**
* Message timestamp.
*/
private long mTimestamp;
/**
* Message type.
*/
private final MessageType mType;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates an eBus message builder instance.
* @param targetClass builds this message class.
* @param type notification, request, or reply message.
*/
protected Builder(final Class extends EMessageObject> targetClass,
final MessageType type)
{
this (targetClass, null, Long.MIN_VALUE, type);
} // end of Builder(Class, MessageType)
/**
* Creates an eBus message builder instance for the given
* message subject.
* @param targetClass builds this message class.
* @param subject message subject.
* @param type notification, request, or reply message.
*/
protected Builder(final Class extends EMessageObject> targetClass,
final String subject,
final MessageType type)
{
this (targetClass,
subject,
System.currentTimeMillis(),
type);
} // end of Builder(String, Class, MessageType)
/**
* Creates an eBus message builder instance for the given
* message subject and timestamp.
* @param timestamp message create timestamp.
* @param subject message subject.
* @param targetClass builds this message class.
* @param type notification, request, or reply message.
*/
protected Builder(final Class extends EMessageObject> targetClass,
final String subject,
final long timestamp,
final MessageType type)
{
super (targetClass);
mSubject = subject;
mTimestamp = timestamp;
mType = type;
} // end of Builder(String, long, Class, MessageType)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Abstract Method Overrides.
//
/**
* Checks if message subject and message type are
* configured. If not, then text explaining this error
* is appended to {@code problems}.
*
* This method should be overridden by subclass message
* builders and called before doing its own
* validation. The first line in the subclass
* {@code validate} implementation should be
* {@code super.validate(problems);}.
*
*
* When overriding this method, be sure to add all
* discovered validation problems to the list. The
* validation method should consist of a series of
* individual {@code if} statements and not
* an {@code if/else if} chain. That way all problems
* are found and not just the first one.
*
*
* Please see {@link Validator} for a
* validation {@code Builder} settings example.
*
* @param problems used to check field validity and
* collect discovered invalid fields.
* @return {@code problems} to allow for method chaining.
*
* @see Validator
*/
@Override
protected Validator validate(final Validator problems)
{
return (
problems.requireNotNull(mSubject, "subject")
.requireNotNull(mType, "message type"));
} // end of validate(Validator)
//
// end of Abstract Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets the message subject to the given value.
* Returns {@code this} builder so that field
* configuration may be chained.
* @param subject message subject.
* @return {@code this} builder instance.
* @throws IllegalArgumentException
* if {@code subject} is either {@code null} or
* empty or length > {@link #MAX_SUBJECT_LENGTH}.
*/
public final B subject(final String subject)
{
if (Strings.isNullOrEmpty(subject))
{
throw (
new IllegalArgumentException(
"subject is null or empty"));
}
if (subject.length() > MAX_SUBJECT_LENGTH)
{
throw (
new IllegalArgumentException(
"subject length > " + MAX_SUBJECT_LENGTH));
}
mSubject = subject;
return ((B) this);
} // end of subject(String)
/**
* Sets the message timestamp to the given value.
* Returns {@code this} builder so that field
* configuration may be chained.
* @param ts message timestamp.
* @return {@code this} builder instance.
*/
public final B timestamp(final long ts)
{
mTimestamp = ts;
return ((B) this);
} // end of timestamp(long)
/**
* Sets the message timestamp to the given value.
* Returns {@code this} builder so that field
* configuration may be chained.
* @param ts message timestamp.
* @return {@code this} builder instance.
*/
public final B timestamp(final Instant ts)
{
mTimestamp = ts.toEpochMilli();
return ((B) this);
} // end of timestamp(final Instant ts)
/**
* Copies the subject and timestamp from the given
* message. This method is not {@code final} allowing
* subclasses to override it. Overriding subclass methods
* must call {@code super.copy(message)} first
* before performing its own message field copies.
*
* If the new message created from a copy of an existing
* message needs an updated timestamp, then either
* {@link #timestamp(long)} or
* {@link #timestamp(Instant)} should be called to update
* the timestamp.
*
* @param message copy subject and timestamp from this
* message.
* @return {@code this} message builder instance.
* @throws NullPointerException
* if {@code message} is {@code null}.
*/
public B copy(final M message)
{
// Copy the subject
mSubject =
(Objects.requireNonNull(
message, "message is null")).subject;
mTimestamp = message.timestamp;
return ((B) this);
} // end of copy(M)
//
// end of Set Methods.
//-------------------------------------------------------
} // end of class Builder
} // end of class EMessage