
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 extends EMessage> mc =
(Class extends EMessage>)
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