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

net.sf.eBus.client.EAbstractConnection 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) 2015, 2016. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.client;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.sysmessages.KeyMessage;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.UnknownMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AsyncChannel;
import net.sf.eBus.util.logging.StatusReport;
import net.sf.eBus.util.logging.StatusReporter;

/**
 * Base class for eBus TCP connections. Maintains the
 * {@link AsyncChannel channel}  connection and
 * {@link AbstractMessageWriter} instance.
 *
 * @author Charles Rapp
 */

/* package */ abstract class EAbstractConnection
{
//---------------------------------------------------------------
// Member methods.
//

    //-----------------------------------------------------------
    // Constructors.
    //

    /**
     * Creates a new eBus connection instance.
     */
    protected EAbstractConnection()
    {
        mAsocket = null;
        mInputReaders = new HashMap<>();
        mOutputWriter = null;
        mBindPort = -1;
    } // end of EAbstractConnection()

    //
    // end of Constructors.
    //-----------------------------------------------------------

    //-----------------------------------------------------------
    // Abstract Method Declarations.
    //

    /**
     * Returns {@code true} if this connection automatically
     * reconnects upon unexpected connection loss.
     * @return {@code true} if automatic reconnection is set
     * and {@code false} otherwise.
     */
    public abstract boolean willReconnect();

    /**
     * Closes the eBus connection after verifying that
     * all previously sent messages have been transmitted. No
     * new messages will be sent after this call and no new
     * messages will be received. If there are any outbound
     * messages waiting to be transmitted, then the connection
     * will be closed once those messages are sent.
     * @see #closeNow
     * @see #send(EMessageHeader)
     */
    /* package */ abstract void close();

    /**
     * Closes the eBus connection immediately. All messages
     * posted for transmission but not completely sent are lost.
     * No new messages may be sent after this point and no new
     * messages will be received.
     * @see #close
     * @see #send(EMessageHeader)
     */
    /* package */ abstract void closeNow();

    /**
     * Establishes a connection to the address specified in
     * {@code config}. Returns {@code true} if the connection is
     * established and {@code false} if the connection is
     * in-progress. If {@code false} is returned,
     * {@link ERemoteApp#handleOpen(EAbstractConnection)} is
     * called when the connection is established.
     * @param config remote connection configuration.
     * @return {@code true} if connection is established and
     * {@code false} if the connection is in-progress.
     * @param config
     * @return
     * @throws IOException
     */
    /* package */ abstract boolean open(EConfigure.RemoteConnection config)
        throws IOException;

    /**
     * Encapsulates an already connected socket accept by an
     * {@link EServer} instance. Will not reconnect.
     * @param socket the accepted socket connection.
     * @param config accept socket configuration.
     * @exception IOException
     * if {@code socket} is closed.
     */
    /* package */ abstract void open(SelectableChannel socket,
                                     EConfigure.Service config)
        throws IOException;

    /**
     * Sends {@code message} to remote eBus application. Since
     * messages are sent asynchronously, then it is likely that
     * the message was not transmitted prior to this
     * method returning. If an application must be certain that
     * all messages are transmitted prior to closing the
     * connection, then {@link #close} should be used rather than
     * {@link #closeNow} as {@link #close} waits for the pending
     * outgoing messages to be transmitted before closing
     * the socket connection. Once a connection is closed, no
     * further messages may be sent.
     * @param header send this header and message.
     * @exception IllegalArgumentException
     * if {@code header} is {@code null}.
     * @exception IllegalStateException
     * if this eBus connection is not connected.
     * @exception IOException
     * if the connection is closed.
     */
    /* package */ abstract void send(EMessageHeader header)
        throws IOException;

    //
    // end of Abstract Method Declarations.
    //-----------------------------------------------------------

    //-----------------------------------------------------------
    // Get Methods.
    //

    /**
     * Returns {@code true} if fully connected to remote eBus
     * and {@code false} otherwise.
     * @return {@code true} if fully connected to remote eBus
     * and {@code false} otherwise.
     */
    public final boolean isOpen()
    {
        return (mAsocket.isOpen());
    } // end of isOpen()

    /**
     * Returns remote eBus' far-end address.
     * If not connected, then returns {@code null}.
     * @return an {@code SocketAddress} value. Will be
     * {@code null} if not connected.
     * @see #localSocketAddress
     */
    public final SocketAddress remoteSocketAddress()
    {
        return (mAsocket.remoteSocketAddress());
    } // end of remoteSocketAddress()

    /**
     * Returns this eBus connection's local address. If not
     * connected, then returns {@code null}.
     * @return the eBus connection's local address.
     * @see #remoteSocketAddress
     */
    public final SocketAddress localSocketAddress()
    {
        return (mAsocket.localSocketAddress());
    } // end of localSocketAddress()

    /**
     * Returns the input buffer size in bytes.
     * @return input buffer capacity.
     */
    public final int inputBufferSize()
    {
        return (mAsocket.inputBufferSize());
    } // end of inputBufferSize()

    /**
     * Returns the output buffer size in bytes.
     * @return output buffer capacity.
     */
    public final int outputBufferSize()
    {
        return (mAsocket.outputBufferSize());
    } // end of outputBufferSize()

    /**
     * Returns the maximum message queue size.
     * @return maximum queue size.
     */
    public final int maxMessageQueueSize()
    {
        return (mOutputWriter.maximumSize());
    } // end of maxMessageQueueSize()

    /**
     * Returns number of messages currently on the queue.
     * @return message queue size.
     */
    public final int messageQueueSize()
    {
        return (mOutputWriter.transmitQueueSize());
    } // end of messageQueueSize()

    //
    // end of Get Methods.
    //-----------------------------------------------------------

    /**
     * Creates a new {@link #mInputReaders} entry based on the
     * {@link KeyMessage} message. If the message contains a
     * class name that is not found in this JVM, then
     * store a {@code null} {@link MessageReader} in the map to
     * signal that the message class is not supported.
     * @param msg the class update message.
     */
    /* package */ final void keyUpdate(final KeyMessage msg)
    {
        final int keyId = msg.keyId;
        MessageReader reader = null;

        try
        {
            @SuppressWarnings ("unchecked")
            final Class mc =
                (Class)
                    Class.forName(msg.keyClass);
            final MessageType mt =
                (MessageType) DataType.findType(mc);
            final MethodHandle mh;
            final String mn;

            // Is this fish, fowl, or flesh?
            // Make that notification, request, or reply.
            if ((ENotificationMessage.class).isAssignableFrom(mc))
            {
                // Notification.
                mh = MESSAGE_CB[NOTIFY_CB];
                mn = CB_METHOD_NAMES[NOTIFY_CB];
            }
            else if ((EReplyMessage.class).isAssignableFrom(mc))
            {
                // Reply.
                mh = MESSAGE_CB[REPLY_CB];
                mn = CB_METHOD_NAMES[REPLY_CB];
            }
            else
            {
                // That leaves request.
                mh = MESSAGE_CB[REQUEST_CB];
                mn = CB_METHOD_NAMES[REQUEST_CB];
            }

            reader =
                new MessageReader(
                    keyId, msg.keySubject, mt, mh, mn);
        }
        catch (ClassNotFoundException classex)
        {
            // Ignore and put null into the readers map.
        }

        mInputReaders.put(keyId, reader);

        return;
    } // end of keyUpdate(KeyMessage)

    /**
     * Initializes the {@link MessageReader} map containing
     * only the system message entries. Application message
     * entries will be added when {@link KeyMessage} arrive.
     */
    protected final void initialize()
    {
        final SystemMessageType[] types =
            SystemMessageType.values();
        final int size = types.length;
        int keyId;
        int index;
        MessageType mt;

        for (index = 0; index < size; ++index)
        {
            keyId = types[index].keyId();
            mt =
                (MessageType)
                    DataType.findType(
                        types[index].messageClass());
            mInputReaders.put(keyId,
                new MessageReader(
                    keyId,
                    AbstractEBusMessage.EBUS_SUBJECT,
                    mt,
                    MESSAGE_CB[index],
                    CB_METHOD_NAMES[index]));
        }

        return;
    } // end of initialize()

    /**
     * Appends this eBus connection status to the report.
     * @param report append the status to this report.
     * @param index the connection index in the map.
     * @param period the status report period in seconds.
     */
    private void reportStatus(final PrintWriter report,
                              final int index,
                              final int period)
    {
        final int msgInCount = mMsgInCount;
        final int msgOutCount = mMsgOutCount;
        final int rateIn = (msgInCount / period);
        final int rateOut = (msgOutCount / period);

        mMsgInCount = 0;
        mMsgOutCount = 0;

        report.format("  [%,d] address: %s%n",
                      index,
                      mAsocket.localSocketAddress());
        report.format(
            "      %,d messages received @ %,d msgs/sec.%n",
            msgInCount,
            rateIn);
        report.format(
            "      %,d messages sent @ %,d msgs/sec.%n",
            msgOutCount,
            rateOut);

        return;
    } // end of reportStatus(PrintWriter, int, int)

//---------------------------------------------------------------
// Member data.
//

    /**
     * The asynchronous socket on which messages are sent and
     * received. This field is not final because the channel
     * constructor takes {@code this} as an argument. That means
     * the socket must be created post construction.
     */
    protected AsyncChannel mAsocket;

    /**
     * Bind the local address to this port. This value is
     * set when the socket is opened, so it cannot be final.
     */
    protected int mBindPort;

    /**
     * Maps the class identifier to the reader responsible for
     * de-serializing it and posting it to the appropriate
     * callback.
     */
    protected final Map mInputReaders;

    /**
     * The message writer is responsible for serializing outbound
     * messages directly to the {@link #mAsocket socket's}
     * output buffer.
     * 

* Like {@link #mAsocket}, this field must be set post * construction. *

*/ protected AbstractMessageWriter mOutputWriter; // // StatusReport statistics. // Not meant to be strictly accurate. // /** * Count up the number of messages received from the remote * eBus engine. */ protected int mMsgInCount; /** * Count up the number of messages sent to the remote eBus * engine. */ protected int mMsgOutCount; //----------------------------------------------------------- // Statics. // /** * Track the existing eBus connections. Used for status * reporting purposes only. */ protected static final Map sConnections = new ConcurrentHashMap<>(); /** * The Java timer thread used for heartbeating, reconnecting, * and other timed socket tasks. */ protected static Timer mTimer = new Timer("ConnTimer", true); /** * Access point for logging. */ private static final Logger sLogger = Logger.getLogger(EAbstractConnection.class.getName()); /** * The total inbound message count for all external clients. */ protected static int mTotalInCount = 0; /** * The total outbound message count for all external clients. */ protected static int mTotalOutCount = 0; //----------------------------------------------------------- // Constants. // /** * This value specifies that there is no limit to the * transmit queue size. */ public static final int DEFAULT_QUEUE_SIZE = 0; /** * A message's serialized size may be at most this many * bytes. Messages longer than that size result in * eBus connection termination. */ public static final int MAX_MESSAGE_SIZE = Short.MAX_VALUE; /** * The message size takes {@value} bytes. */ protected static final int MESSAGE_SIZE_SIZE = 4; /** * A message header size is {@value} bytes. */ protected static final int MESSAGE_HEADER_SIZE = 16; /** * Heartbeat and heartbeat reply messages are negative * two-byte integers. */ protected static final int HEARTBEAT = -15000; /** * There is a separate reply message for a heartbeat. */ protected static final int HEARTBEAT_REPLY = -8000; /** * The serialized heartbeat message. */ protected static final byte[] HEARTBEAT_DATA; /** * The serialized heartbeat reply message. */ protected static final byte[] HEARTBEAT_REPLY_DATA; // // Message callback MethodHandles. // /** * Well known method names for processing messages. */ private static final String[] CB_METHOD_NAMES = { "remoteAd", // AdMessage. "remoteCancelRequest", // CancelRequest "remoteClassUpdate", // KeyMessage "remoteFeedStatus", // FeedStatusMessage "remoteLogoff", // LogoffMessage "remoteLogonComplete", // LogonComplete "remoteLogon", // Logon "remoteLogonReply", // LogonReply "remoteRequestAck", // RemoteAck "remoteSubscribe", // SubscribeMessage "remoteNotify", // Notification "remoteRequest", // Request "remoteReply" // Reply }; /** * Pass application notification messages to "remoteNotify". */ protected static final int NOTIFY_CB = 10; /** * Pass application request messages to "remoteRequest". */ protected static final int REQUEST_CB = 11; /** * Pass application reply messages to "remoteReply" */ protected static final int REPLY_CB = 12; /** * Message callback method handles. */ protected static final MethodHandle[] MESSAGE_CB; /** * The connection is open callback. */ protected static final int OPEN_CB = 0; /** * The connection is closed callback. */ protected static final int CLOSE_CB = 1; /** * Call this {@code ERemoteApp} methods to report changes in * the connection state. */ protected static final String[] CONN_CB_METHOD_NAMES = { "handleOpen", "handleClose" }; /** * Callbacks for reporting when the connection is up, down, * or en error occurred. */ protected static final MethodHandle[] CONN_CB; /** * The message type is used to serialize and de-serialize an * eBus message. */ protected static final DataType MESSAGE_TYPE = DataType.findType(EMessage.class); //--------------------------------------------------------------- // Inner classes. // /** * This class combines two data points: the message type for * de-serializing a particular message class and the handle * for the method responsible for processing the message. * This class extracts messages from a buffer and forwards * them to the target. */ protected final class MessageReader { //--------------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new message reader for the given parameters. * @param keyId unique message class identifier from * remote eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. */ private MessageReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn) { _keyId = keyId; _subject = subject; _messageType = mt; _callback = mh; _methodName = mn; } // end of MessageReader(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // /** * Returns the message key identifier. * @return message key identifier. */ public int keyId() { return (_keyId); } // end of keyId() // // end of Get methods. //------------------------------------------------------- /** * Returns the message header extracted from the given * buffer, starting at the buffer's current position. * @param buffer extract the message from this buffer. * @param address data came from this source address. * @return the extracted message header and message. * @throws BufferUnderflowException * if {@code buffer} contains an incomplete message. * @throws UnknownMessageException * if the de-serialized class identifier references an * unknown message class. * @throws InvalidMessageException * if the message construction fails. */ public EMessageHeader extractMessage(final ByteBuffer buffer, final SocketAddress address) throws BufferUnderflowException, UnknownMessageException, InvalidMessageException { final int fromFeedId = buffer.getInt(); final int toFeedId = buffer.getInt(); final EMessage msg; final EMessageHeader retval; // Need to set the message subject explicitly // because it is not longer serialized. Rather the // subject is inferred from the message key // identifier. _messageType.subject(_subject); msg = (EMessage) _messageType.deserialize(buffer); retval = new EMessageHeader(_keyId, fromFeedId, toFeedId, address, msg); ++mMsgInCount; ++mTotalInCount; if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest(String.format("%s: handling %s message:%n From ID: %d%n To ID: %d%n%s", mAsocket.remoteSocketAddress(), retval.messageClass(), retval.fromFeedId(), retval.toFeedId(), msg)); } else if (sLogger.isLoggable(Level.FINER)) { sLogger.finer(String.format("%s: handling %s message (from=%d, to=%d).", mAsocket.remoteSocketAddress(), retval.messageClass(), retval.fromFeedId(), retval.toFeedId())); } return (retval); } // end of extractMessage(ByteBuffer) /** * Forwards the given message header to the specified * eBus remote application instance. * @param header forward this message header to * {@code target}. * @param target the eBus remote application instance * to receive the inbound message. */ public void forwardMessage(final EMessageHeader header, final ERemoteApp target) { try { if (sLogger.isLoggable(Level.FINEST)) { final Formatter output = new Formatter(); final MethodType mt = _callback.type(); final Class[] parms = mt.parameterArray(); final int nParms = parms.length; int index; String sep; output.format("%s: forward %s to %s %s.%s", mAsocket.remoteSocketAddress(), header.messageKey(), (mt.returnType()).getName(), parms[0].getName(), _methodName); for (index = 1, sep = "("; index < nParms; ++index, sep = ", ") { output.format( "%s%s", sep, parms[index].getName()); } output.format(")"); sLogger.finest(output.toString()); } _callback.invokeExact(target, header); } catch (Throwable tex) { sLogger.log(Level.WARNING, "Error processing message header.", tex); } return; } // end of forwardMessage(EMessageHeader) //----------------------------------------------------------- // Member data. // /** * A unique message key identifier received from the * remote eBus application. */ private final int _keyId; /** * The message key subject. Needed to construct inbound * messages. */ private final String _subject; /** * The message type for deserializing the message class. */ private final MessageType _messageType; /** * Forward the message to this callback method. */ private final MethodHandle _callback; /** * The callback method name (not included in the method * handle). */ private final String _methodName; } // end of class MessageReader /** * This class is responsible for serializing messages * directly to a given {@link AsyncChannel channel} * {@link ByteBuffer output buffer}. */ protected static abstract class AbstractMessageWriter { //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a message writer instance with the given * maximum outbound message queue size and eBus * connection. * @param maxSize maximum outbound message queue size. * @param conn this message writer works for this eBus * connection. */ protected AbstractMessageWriter(final int maxSize, final EAbstractConnection conn) { _connection = conn; _maxSize = maxSize; _transmitQueue = new ConcurrentLinkedQueue<>(); _transmitQueueSize = new AtomicInteger(); _closingFlag = false; _transmitCount = 0; _discardCount = 0; } // end of AbstractMessageWriter(int, EConnection) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // /** * Returns the transmit queue's maximum size. If * {@link #DEFAULT_QUEUE_SIZE} is returned, then the * queue size is unlimited. If a value > * {@code DEFAULT_QUEUE_SIZE} is returned, then when the * queue reaches this limit and attempts to offer a new * message, the new message is discarded and a buffer * overflow exception thrown * @return the transmit queue maximum size. */ public final int maximumSize() { return (_maxSize); } // end of maximumSize() /** * Returns the current message transmit count. Since this * method is not synchronized the returned value is * approximate. * @return the current message transmit count. */ public final int transmitCount() { return (_transmitCount); } // end of transmitCount() /** * Returns the number of discarded user messages. Since * this method is not synchronized the value is * approximate. * @return the number of discarded user messages. */ public final int discardCount() { return (_discardCount); } // end of discardCount() /** * Returns the transmit queue current size. This * is an approximate value and should not be * used to determine if the queue is empty. * @return approximate transmit queue size. */ public final int transmitQueueSize() { return (_transmitQueueSize.get()); } // end of transmitQueueSize() /** * Returns {@code true} if this message writer has * outbound messages pending and {@code false} otherwise. * @return {@code true} if there are messages waiting to * be transmitted. */ public final boolean hasMessages() { return (!_transmitQueue.isEmpty()); } // end of hasMessages() // // end of Get methods. //------------------------------------------------------- /** * Queues the message header. If the maximum queue * size is reached, then the header is dropped and a * buffer overflow exception is thrown. *

* If the header contains a * {@link net.sf.eBus.messages.ESystemMessage system message}, * then the message is queued despite exceeding the * maximum queue size because system message must get * through. *

*

* Returns {@code true} if this is the first message on * the queue. This means the caller is clear to send the * queued message since no other thread is currently * accessing the channel. *

* @param h enqueue this message header. * @return {@code true} if this message writer should be * posted to the socket. * @exception BufferOverflowException * if the transmit queue maximum size is reached. */ public final boolean post(final EMessageHeader h) throws BufferOverflowException { // Increment the queue size if-and-only-if the // message type is *not* system. final EMessage.MessageType msgType = h.messageType(); final int queueSize = (msgType == EMessage.MessageType.SYSTEM ? (_transmitQueueSize.get() + 1) : _transmitQueueSize.incrementAndGet()); // If the queue is *not* of unlimited size and the // queue has reached that limit, then let the caller // know about this by throwing an overflow exception. // Put always add system messages. if (_maxSize > DEFAULT_QUEUE_SIZE && queueSize >= _maxSize) { final BufferOverflowException bufex = new BufferOverflowException(); final IllegalStateException statex = new IllegalStateException( String.format( "message queue maximum reached (%,d)", _maxSize)); bufex.initCause(statex); if (_sublogger.isLoggable(Level.FINE)) { _sublogger.fine( String.format( "%s queue: queue maximum reached (%,d).", _connection.remoteSocketAddress(), _maxSize)); } ++_discardCount; // HEAVE! throw (bufex); } _transmitQueue.offer(h); if (_sublogger.isLoggable(Level.FINER)) { _sublogger.finer( String.format( "%s queue: added message (size=%,d, transmited=%,d, discarded=%,d).", _connection.remoteSocketAddress(), queueSize, _transmitCount, _discardCount)); } // Pass this message writer to the socket if this is // the first message on the queue and the socket // is not overflowing. return (queueSize == 1); } // end of post(EMessageHeader) /** * Sets the closing flag to {@code true}. This is the * only possible setting because, once set, it cannot be * changed back. */ protected final void setClosing() { _closingFlag = true; return; } // end of setClosing() /** * Closes this message writer by discarding all unsent * messages. */ protected final void closed() { _transmitQueue.clear(); _transmitQueueSize.set(0); return; } // end of closed() //----------------------------------------------------------- // Member data. // /** * This message writer works for this eBus connection. */ protected final EAbstractConnection _connection; /** * The message queue maximum allowed size. If the value * is ≤ zero, then the queue has no maximum. Once the * maximum is reached, new messages are rejected. */ protected final int _maxSize; /** * Post messages here until it is time to serialize them * to the socket output buffer. Then serialize as many * as possible. */ protected final Queue _transmitQueue; /** * Tracks the {@link #_transmitQueue transmit queue} * approximate size. This is necessary * because {@code ConcurrentLinkedDeque.size()} is a * costly operation. */ protected final AtomicInteger _transmitQueueSize; /** * Set to {@code true} when the parent eBus connection is * doing a slow close. */ protected volatile boolean _closingFlag; /** * Count up the number of messages transmitted. Meant for * reporting purposes only. */ protected int _transmitCount; /** * Count up the number of discarded application messages. * Meant for reporting purposes only. */ protected int _discardCount; //------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger _sublogger = Logger.getLogger(AbstractMessageWriter.class.getName()); } // end of class MessageWriter /** * This class adds the status of all remote application * connections to the status report. */ private static final class EConnectionStatusReporter implements StatusReporter { //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * This constructor has no member data to initialize. */ public EConnectionStatusReporter() {} // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // StatusReporter Interface Implementation. // /** * Add the remote application connection status to the * report. * @param report the logged status report. */ @Override public void reportStatus(final PrintWriter report) { final int period = (int) (((StatusReport.getInstance()). getReportFrequency()).getFrequency() / 1000L); final int appCount = sConnections.size(); final int totalInCount = mTotalInCount; final int totalOutCount = mTotalOutCount; final int totalInRate = (totalInCount / period); final int totalOutRate = (totalOutCount / period); mTotalInCount = 0; mTotalOutCount = 0; report.print("ERemote: "); if (appCount == 0) { report.println( "there are no remote connections."); } else { final Collection apps = new ArrayList<>(sConnections.values()); int index = 0; report.format( "there is %,d remote application %s.%n", appCount, (appCount == 1 ? "connection" : "connections")); for (EAbstractConnection connection : apps) { connection.reportStatus( report, index, period); report.println(); ++index; } report.format( " %,d total messages received @ %,d msgs/sec.%n", totalInCount, totalInRate); report.format( " %,d total messages sent @ %,d msgs/sec.", totalOutCount, totalOutRate); apps.clear(); } return; } // end of reportStatus(PrintWriter) // // end of StatusReporter Interface Implementation. //------------------------------------------------------- //----------------------------------------------------------- // Member data. // } // end of class EConnectionStatusReporter static { ByteBuffer buffer = ByteBuffer.allocate(4); final MethodHandles.Lookup lookup = MethodHandles.lookup(); int size; int index = 0; // Create the heartbeat and heartbeat reply messages. HEARTBEAT_DATA = new byte[2]; HEARTBEAT_REPLY_DATA = new byte[2]; buffer.putInt(HEARTBEAT); buffer.flip(); buffer.get(HEARTBEAT_DATA); buffer.clear(); buffer.putInt(HEARTBEAT_REPLY); buffer.flip(); buffer.get(HEARTBEAT_REPLY_DATA); buffer.clear(); size = CB_METHOD_NAMES.length; MESSAGE_CB = new MethodHandle[size]; // Look up the message callback methods. try { final MethodType mt = MethodType.methodType( void.class, EMessageHeader.class); for (index = 0; index < size; ++index) { MESSAGE_CB[index] = lookup.findVirtual( ERemoteApp.class, CB_METHOD_NAMES[index], mt); } } catch (NoSuchMethodException | IllegalAccessException jex) { sLogger.log( Level.SEVERE, CB_METHOD_NAMES[index] + " lookup failed", jex); } size = CONN_CB_METHOD_NAMES.length; CONN_CB = new MethodHandle[size]; // Look up the connection callback methods. try { final MethodType mt = MethodType.methodType( void.class, EAbstractConnection.class); for (index = 0; index < size; ++index) { CONN_CB[index] = lookup.findVirtual( ERemoteApp.class, CONN_CB_METHOD_NAMES[index], mt); } } catch (NoSuchMethodException | IllegalAccessException jex) { sLogger.log( Level.SEVERE, CONN_CB_METHOD_NAMES[index] + " lookup failed", jex); } (StatusReport.getInstance()).register( new EAbstractConnection.EConnectionStatusReporter()); } // end of static } // end of class EAbstractConnection




© 2015 - 2025 Weber Informatics LLC | Privacy Policy