All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.messages.EMessage Maven / Gradle / Ivy

The newest version!
//
// 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 mc) { mMsgClass = mc; } // end of MessageType(Class) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the associated message class. * @return message class. */ public Class 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 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 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 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 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy