Message.java

/*
** Module   : Message.java
** Abstract : container for the most elemental protocol transmissions
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description------------------------------
** 001 SIY 20050113   @19497 Created initial version.
** 002 SIY 20050211   @19772 Added replacement for the standard
**                           object reading/writing methods and copying
**                           of routing keys.
** 003 SIY 20050315   @20382 Organized imports, fixed spelling and other 
**                           minor cleanups.
** 004 GES 20060726   @28198 Added a copy constructor and cleaned up the
**                           formatting to meet coding standards.
** 005 GES 20060801   @28288 Converted to Externalizable to improve
**                           efficiency because Serializable has unneeded
**                           processing (for versioning...) that is
**                           executed on "inflation".
** 006 GES 20070111   @31786 Moved message types to separate interface
**                           so calling code can more easily reference
**                           them.  Added documentation.
** 007 GES 20070115   @31833 Added support for conversation mode.
** 008 ECF 20071115   @35882 Refactored net package. isRouterRequest() now
**                           considers new INIT_ROUTER message type.
** 009 CA  20130822          Fixed the net protocol to support async requests.
** 010 IAS 20200922          Added helper methods for Byteman, changes payload serialization/ 
** 011 IAS 20200930          Added JMX counters.
*
** 012 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 013 HC  20240222          Enabled JMX on FWD Client.
** 014 CA  20240809          Skip using JMX timers when JMX_DEBUG flag is not set.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.net;

import java.io.*;
import java.util.logging.*;

import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;

/**
 * This class represents messages which are used to exchange information via
 * the distributed application protocol.
 * <p>
 * Each message has a type ({@link MessageTypes}) which defines the how the
 * message is to be treated.
 * <p>
 * Synchronous uses of the protocol (e.g. for a remote object or remote
 * procedure call implementation) us the request ID field.  This is an
 * integer code used to match requests and replies.
 * <p>
 * There is always a {@link RoutingKey} which specifies the destination.
 * <p>
 * There is a <code>Serializable</code> (or <code>Externalizable</code>)
 * payload for each message.  This should not be <code>null</code> but to
 * simulate a <code>null</code> payload, one can use the following (a zero
 * sized array of objects):
 * <p>
 * <pre>
 * new Object[] {}
 * </pre>
 * <p>
 * Implementing your payload completely as an <code>Externalizable</code> is
 * highly recommended as it will improve performance.
 */
public class Message
implements MessageTypes,
           Externalizable
{
   /** timer for measuring (de)serialization time */
   private static final NanoTimer NANO_TIMER = NanoTimer.getInstance(
      FwdServerJMX.TimeStat.MessagePayloadS11n
   );

   /** Logger. */
   private static final CentralLogger LOG = CentralLogger.get(Message.class);

   /** Message type. */
   private int type = 0;
   
   /** Request ID (only used with a synchronous request/reply). */
   private int requestID = 0;
   
   /** Routing and requested method information. */
   private RoutingKey key = null;

   /** Message payload. */
   private Serializable payload = null;

   /**
    * Constructs an empty message. Assumes REQUEST_SYNCH message type. A
    * valid <code>RoutingKey</code> instance should be assigned to the
    * message before it can be sent.
    */
   public Message()
   {
      type = REQUEST_SYNCH;
   }

   /**
    * Constructs an empty message of specified type. A valid
    * <code>RoutingKey</code> instance should be assigned to the message
    * before it can be sent.
    * 
    * @param    type
    *           Message type.
    */
   public Message(int type)
   {
      this.type = type;
   }

   /**
    * Constructs a complete message.
    * 
    * @param    type
    *           Message type.
    * @param    requestID
    *           Synchronous request ID assigned to message.
    * @param    key
    *           An instance of <code>RoutingKey</code> which points to
    *           required entry point/node.
    * @param    payload
    *           Message payload.
    */
   public Message(int          type,
                  int          requestID,
                  RoutingKey   key,
                  Serializable payload)
   {
      this.type      = type;
      this.requestID = requestID;
      this.key       = new RoutingKey(key);
      this.payload   = payload;
   }

   /**
    * Constructs a message of specified type with specified
    * <code>RoutingKey</code> and payload.
    * 
    * @param    type
    *           Message type.
    * @param    key
    *           An instance of <code>RoutingKey</code> which points to
    *           required entry point/node.
    * @param    payload
    *           Message payload.
    */
   public Message(int type, RoutingKey key, Serializable payload)
   {
      this(type, 0, key, payload);
   }

   /**
    * Constructs a message of specified type and with provided payload. A
    * valid <code>RoutingKey</code> instance should be assigned to the
    * message before it can be sent.
    * 
    * @param    type
    *           Message type.
    * @param    payload
    *           Message payload.
    */
   public Message(int type, Serializable payload)
   {
      this(type, 0, null, payload);
   }

   /**
    * Constructs a synchronous request message with specified
    * <code>RoutingKey</code> and payload.
    * 
    * @param    key
    *           An instance of <code>RoutingKey</code> which points to
    *           required entry point/node.
    * @param    payload
    *           Message payload.
    */
   public Message(RoutingKey key, Serializable payload)
   {
      this(REQUEST_SYNCH, 0, key, payload);
   }

   /**
    * Copy constructor.
    * 
    * @param    msg
    *           Message to copy.  Must not be <code>null</code>.
    */
   public Message(Message msg)
   {
      this.type      = msg.type;
      this.requestID = msg.requestID;
      this.key       = new RoutingKey(msg.key);
      this.payload   = msg.payload;
   }

   /**
    * Get <code>RoutingKey</code> instance assigned to message.
    * 
    * @return   The key stored in the message.
    */
   public RoutingKey getKey()
   {
      return key;
   }

   /**
    * Get the message payload.
    * 
    * @return   The message payload.
    */
   public Serializable getPayload()
   {
      return payload;
   }

   /**
    * Get the synchronous request ID.
    * 
    * @return   The message request ID.
    */
   public int getRequestID()
   {
      return requestID;
   }

   /**
    * Get the message type.
    * 
    * @return   The message type.
    */
   public int getType()
   {
      return type;
   }

   /**
    * Determine if the message is one of the types processed by the
    * <code>Dispatcher</code>.
    * 
    * @return   <code>true</code> if the message must be processed by the
    *           <code>Dispatcher</code>.
    */
   public boolean isDispatcherRequest()
   {
      return (type == REQUEST_SYNCH || type == REQUEST_ASYNCH);
   }

   /**
    * Check if message is an ECHO request.
    * 
    * @return   <code>true</code> if the message has type <code>ECHO</code>.
    */
   public boolean isEchoRequest()
   {
      return (type == ECHO);
   }

   /**
    * Determine if message is a reply (<code>ADDRESS_REPLY, ECHO_REPLY,
    * INIT_REPLY, REPLY, REPLY_EXCEPTION</code>).
    * 
    * @return   <code>true</code> if the message is any form of reply.
    */
   public boolean isReply()
   {
      return (type >= BEGIN_REPLY && type <= END_REPLY);
   }

   /**
    * Determine if the message is an async reply (the type is {@link MessageTypes#REPLY_ASYNC}, 
    * {@link MessageTypes#REPLY_EXCEPTION_ASYNC} or the routing key is part of the 
    * {@link RoutingKey#INTERRUPT_SESSION} group).
    * 
    * @return   <code>true</code> if the message is an async reply.
    */
   public boolean isAsyncReply()
   {
      return type == REPLY_ASYNC           ||
             type == REPLY_EXCEPTION_ASYNC || 
             key.getGroupID() == RoutingKey.INTERRUPT_SESSION; 
   }

   /**
    * Determine if the message is an async request (the type is {@link MessageTypes#REQUEST_ASYNCH} 
    * or the routing key is part of the {@link RoutingKey#INTERRUPT_SESSION} group).
    * 
    * @return   <code>true</code> if the message is an async reply.
    */
   public boolean isAsyncRequest()
   {
      return type == REQUEST_ASYNCH || key.getGroupID() == RoutingKey.INTERRUPT_SESSION; 
   }

   /**
    * Determine if the message is one of types which are processed by the
    * <code>Router</code>.
    * 
    * @return   <code>true</code> if the message must be processed by the
    *           <code>Router</code>.
    */
   public boolean isRouterRequest()
   {
      return (type == INIT_STANDARD     ||
              type == INIT_CONVERSATION ||
              type == INIT_ROUTER       ||
              type == ADDRESS_REQUEST);
   }

   /**
    * Assign <code>RoutingKey</code> to the message.
    * 
    * @param    key
    *           New instance of <code>RoutingKey</code>.
    */
   public void setKey(RoutingKey key)
   {
      this.key = new RoutingKey(key);
   }

   /**
    * Assign new payload to the message.
    * 
    * @param    payload
    *           New message payload.
    */
   public void setPayload(Serializable payload)
   {
      this.payload = payload;
   }

   /**
    * Assigns synchronous request ID to message.
    * 
    * @param    requestID
    *           New synchronous request ID.
    */
   public void setRequestID(int requestID)
   {
      this.requestID = requestID;
   }

   /**
    * Change message type to specified one.
    * 
    * @param    type
    *           New message type.
    */
   public void setType(int type)
   {
      this.type = type;
   }

   /**
    * Replacement for the default object reading method. Note that instead of
    * retrieving of the "unknown" <code>RoutingKey</code> instance for the
    * <code>key</code> it is explicitly instantiated and then its fields are
    * restored by calling the appropriate method.
    * 
    * @param    in
    *           The input source from which fields will be restored.
    *
    * @throws   IOException
    *           In case of I/O errors.
    * @throws   ClassNotFoundException
    *           If payload can't be instantiated.
    */
   public void readExternal(ObjectInput in)
   throws IOException,
          ClassNotFoundException
   {
      type      = in.readInt();
      requestID = in.readInt();
      
      key = new RoutingKey();
      key.readExternal(in);

      if (FwdServerJMX.JMX_DEBUG)
      {
         NANO_TIMER.timer(() -> setPayload(PayloadSerializer.readPayload(in)));
      }
      else
      {
         setPayload(PayloadSerializer.readPayload(in));
      }
//      onRead();
   }


   /**
    * Replacement for the default object writing method. Note that instead of
    * saving <code>key</code> as an ordinary object, it's saved by calling
    * appropriate method. This can be done because there is no need to save
    * type information in the stream so we can save few bytes of packet size.
    * 
    * @param    out
    *           The output destination to which fields will be saved.
    *
    * @throws   IOException
    *           In case of I/O errors.
    */
   public void writeExternal(ObjectOutput out)
   throws IOException
   {
      out.writeInt(type);
      out.writeInt(requestID);
      key.writeExternal(out);
//      onWrite();
      if (FwdServerJMX.JMX_DEBUG)
      {
         NANO_TIMER.timer(() -> PayloadSerializer.writePayload(out, payload));
      }
      else
      {
         PayloadSerializer.writePayload(out, payload);
      }
   }
      
   /*
The following methods can be used with the following Byteman script to trace messages types:

  RULE onRead
  CLASS com.goldencode.p2j.net.Message
  METHOD readExternal
  AT EXIT
  BIND msg:Message = $0;
       ok:boolean = msg.onRead();
  IF FALSE
  DO debug("");
  ENDRULE
  
  RULE onWrite
  CLASS com.goldencode.p2j.net.Message
  METHOD writeExternal
  AT ENTRY
  BIND msg:Message = $0;
       ok:boolean = msg.onWrite();
  IF FALSE
  DO debug("");
  ENDRULE
*/
   /**
    * Helper method for Byteman tracing
    * @return always <code>true</code>
    */
   public boolean onRead()
   {
      LOG.log(Level.FINEST, "!!!Deserialized payload; type: %s for %s: %s", type, requestID,
              PayloadSerializer.dumpObject(payload));
      return true;
   }
   
   /**
    * Helper method for Byteman tracing
    * @return always <code>true</code>
    */
   public boolean onWrite()
   {
      LOG.log(Level.FINEST, "!!!Serializing payload; type: %s for %s: %s", type, requestID, 
            PayloadSerializer.dumpObject(payload));
      return true;
   }
}