net.sf.eBus.config.EConfigure Maven / Gradle / Ivy
//
// Copyright 2008 - 2013, 2015 - 2018, 2020 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.config;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigObject;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ProtocolFamily;
import java.net.SocketException;
import java.net.StandardProtocolFamily;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import static net.sf.eBus.config.ENetConfigure.ANY_PORT;
import static net.sf.eBus.config.ENetConfigure.DEFAULT_BUFFER_SIZE;
import static net.sf.eBus.config.ENetConfigure.MAX_PORT;
import static net.sf.eBus.config.ENetConfigure.MIN_PORT;
import net.sf.eBus.config.ThreadAffinityConfigure.AffinityType;
import net.sf.eBus.util.ValidationException;
import net.sf.eBus.util.Validator;
import net.sf.eBus.util.regex.Pattern;
/**
* This immutable class contains eBus services, remote
* connection, dispatcher, and pause configuration. This
* configuration can be specified:
*
* -
* By building {@link EConfigure.Service} and
* {@link EConfigure.RemoteConnection} instances and passing
* these objects to
* {@code EServer.configure(EConfigure)} and
* {@code ERemoteApp.configure(EConfigure)}, respectively.
* {@link EConfigure.Dispatcher} has a private constructor
* and cannot be instantiated by the application.
*
* -
* Creating individual {@link EConfigure.Service} and
* {@link EConfigure.RemoteConnection} instances and passing
* the instance to {@code EServer.openServer} or
* {@code ERemoteApp.openConnection}, respectively. This
* allows for finer-grained control over services and
* connections.
*
* -
* From typesafe
* JSON-based configuration file.
*
*
*
* NOTE:As of eBus release 6.0.0,
* {@code Properites}-based configuration is no longer supported.
* Please move to typesafe-based configuration.
*
*
* Please note that eBus configuration requires no service or
* connections to be specified. If this is the case, remote
* connections are neither accepted nor established and all
* messages flow between objects within the JVM.
*
*
* eBus release 4.7.0 supports secure TCP connections based on
* SSL/TLS using {@link SSLContext}. eBus release 6.0.0 supports
* secure UDP "connections" based on DTLS. These connection types
* are not supported using a configuration file as this
* would require placing sensitive information in an insecure
* manner. Therefore, creating secure TCP services and
* connections can only be done using the API.
*
*
* Dispatcher threads are instantiated on JVM start-up, the
* dispatcher configuration is taken from the properties file
* specified by the command line parameter
* {@code -Dnet.sf.eBus.config.jsonFile=}<typesafe json file path>.
* eBus loads the {@code EClientDispatcher} configuration from
* this properties file and creates the dispatcher threads
* according to this configuration. If no configuration file is
* specified or is incorrectly configured, eBus defaults to a
* single, blocking run queue. The run queue thread count equals
* {@link Runtime#availableProcessors()}.
*
*
* The eBus configuration uses the following JSON properties. See
* typesafe project
* for a thorough explanation of typesafe JSON configuration
* file layout. As of eBus v. 5.1.0, eBus connections may be
* paused which is described in detail
* here.
*
*
* Top-level JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code services}
* No
*
* Array of {@link EConfigure.Service} instances.
*
* No eBus services.
*
* Contains eBus services. May be an empty array.
*
*
*
* {@code connections}
* No
*
* Array of {@link RemoteConnection} instances.
*
* No eBus remote connections.
*
* Contains eBus remote connections. May be an empty array.
*
*
*
* {@code multicast}
* No
*
* Array of {@link EConfigure.MulticastConnection}
* instances.
*
* No eBus multicast notifications.
*
* Contains eBus multicast connections. May be an empty
* array.
*
*
*
* {@code dispatchers}
* No.
*
* Array of {@link Dispatcher} instances.
*
*
* Default eBus blocking dispatcher with
* {@link Thread#NORM_PRIORITY} and
* {@link EConfigure#DEFAULT_QUANTUM} and thread count
* based on the number of available processors.
*
*
* Contains eBus dispatcher definitions. May be an empty
* array.
*
*
*
* {@code selectors}
* No
*
* Array of {@link ENetConfigure.SelectorInfo} instances.
*
*
*
* Contains eBus NIO selector thread definitions. May be an
* empty array.
*
*
*
* See {@link EConfigure.Dispatcher} for information about two
* special, pre-defined {@code Dispatcher} types:
* {@link DispatcherType#SWING} and {@link DispatcherType#JAVAFX}
* which deliver eBus messages via the GUI thread.
*
* See the eBus overview section on connecting eBus applications
* for a detailed description in configuring eBus connections.
*
*
* Connection Pause
*
*
* eBus release 5.1.0 introduces the ability to automatically
* pause a connection when it has been idle or continuously
* connected for a given time. When paused, messages are kept on
* a backlog for later transmission when the connection is
* resumed. The message backlog queue size may be limited. Once
* the queue size reaches that limit, then messages are discarded
* based on the specified discard policy.
*
*
* The connection is resumed when the pause time is reached.
* This pause time is always used.
* Optionally a resume-on-backlog-size may also be set. When
* the backlog size reaches this size while paused, the
* connection is resumed even if the pause time is not yet
* reached.
*
*
* The connection pause time and maximum backlog size are
* negotiated between the two connection ends. The lesser pause
* time and backlog size values are used by both ends.
*
*
* This feature is added with an eye towards mobile devices which
* cannot support keeping connections up for an extended time due
* to battery limitations.
*
*
* eBus JSON Configuration
*
*
* eBus now supports
* typesafe
* JSON configuration. This represents a subset of JSON known as
* HOCON (Human-Optimized Config Object Notation).
* The following is an eBus configuration in HOCON. Going forward
* typesafe HOCON will be the preferred method for configuring
* eBus with {@code java.util.Properties} being deprecated.
*
* selectors : [
{
name : selector1
type : "spin+park"
isDefault : true
priority : 7
spinLimit : 1000000
parkTime : 500ns
}
]
services : [
{
name : service1 // Defaults to TCP connection type.
port : 12345
addressFilter : [
"127.0.0.1",
"127.0.0.1:54321"
]
serviceSelector : selector1
connectionSelector : selector1
byteOrder : LITTLE_ENDIAN
inputBufferSize : 8192
outputBufferSize : 65536
messageQueueSize : 100
canPause : true
// Connection pause/resume configuration.
pause : {
pauseTime : 10m
maxBacklogSize : 50
}
}
]
connections : [
{
name : conn1
host : "127.0.0.1"
port : 12346
bindPort : 0
byteOrder : BIG_ENDIAN
selector : selector1
inputBufferSize : 8192
outputBufferSize : 65536
messageQueueSize : 100
reconnect : true
reconnectTime : 500ms
canPause : true
// Connection pause/resume configuration.
pause : {
pauseTime : 5m
maxBacklogSize : 100
discardPolicy : YOUNGEST_FIRST
idleTime : 1m
maxConnectTime : 2m
resumeOnBacklogSize : 10
}
}
]
dispatchers : [
{
name : d1
runQueueType : "spin+park"
spinLimit : 2500000
parkTime : 500ns
isDefault : true
priority : 5
quantum : 10000ns
numberThreads : 4
}
]
*
* @see ENetConfigure
*
* @author Charles Rapp
*/
public final class EConfigure
{
//---------------------------------------------------------------
// Member Enums.
//
/**
* Enumerates the channel types supported by eBus
* {@code ERemoteApp}. This is currently limited to either
* TCP or secure TCP (TLS - Transport Layer Security).
*/
public enum ConnectionType
{
/**
* Remote application connection is a TCP socket.
*/
TCP (true, false, null),
/**
* Remote application connection is a TCP socket using
* TLS (Transport Layer Security) to provide
* communication security.
*/
SECURE_TCP (true, true, TLS_PROTOCOL_NAME),
/**
* Remote application connection is a UDP socket.
*/
UDP (false, false, null),
/**
* Remote application connection is a UDP socket using
* DTLS (Datagram Transport Layer Security) to provide
* communication security.
*/
SECURE_UDP (false, true, DTLS_PROTOCOL_NAME);
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Set to {@code true} if this is a TCP connection type.
*/
private final boolean mIsTcp;
/**
* Set to {@code true} if this is a secure connection
* type and {@code false} if a clear text connection.
*/
private final boolean mIsSecure;
/**
* If {@link #mIsSecure} is {@code true}, then this is
* the SSL protocol associated with the secure connection
* type. Otherwise is set to {@code null}.
*/
private final String mSSLProtocol;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private ConnectionType(final boolean tcpFlag,
final boolean secureFlag,
final String sslProtocol)
{
mIsTcp = tcpFlag;
mIsSecure = secureFlag;
mSSLProtocol = sslProtocol;
} // end of ConnectionType(boolean, boolean, String)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns {@code true} if this is a TCP connection type
* and {@code false} otherwise.
* @return {@code true} if a TCP connection type.
*/
public boolean isTcp()
{
return (mIsTcp);
} // end of isTcp()
/**
* Returns {@code true} if this is a secure connection
* type and {@code fales} if a clear text connection.
* @return {@code true} if connection type is secure.
*/
public boolean isSecure()
{
return (mIsSecure);
} // end of isSecure()
/**
* Returns SSL protocol name associated with a secure
* connection type. If connection type is insecure, then
* returns {@code null}.
* @return SSL protocol name or {@code null} if
* connection type is insecure.
*/
public String sslProtocol()
{
return (mSSLProtocol);
} // end of sslProtocol()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of ConnectionType
/**
* Enumerates the supported eBus dispatcher thread types.
* There are effectively two dispatcher types: eBus and
* GUI with GUI divided between Swing and JavaFX.
*/
public enum DispatcherType
{
/**
* Default eBus dispatcher based on a run queue, so the
* dispatcher method is
* {@code EClient.doDispatch(Runnable)}.
*/
EBUS (false, null),
/**
* Posts a task to the JavaFX GUI thread using
* {@code javafx.application.Platform.runLater(Runnable)}.
*/
JAVAFX (true,
task ->
{
// If currently running in the JavaFX GUI
// thread, then execute the task immediately.
if (javafx.application.Platform.isFxApplicationThread())
{
try
{
task.run();
}
catch (Exception jex)
{
sLogger.warning(
jex.getLocalizedMessage());
}
}
else
{
javafx.application.Platform.runLater(task);
}
}),
/**
* Posts a task to the Swing GUI thread using
* {@code javax.swing.SwingUtilities.invokeLater(Runnable)}.
*/
SWING (true,
task ->
{
// If currently running in the Swing GUI
// thread, then execute the task immediately.
if (javax.swing.SwingUtilities.isEventDispatchThread())
{
try
{
task.run();
}
catch (Exception jex)
{
sLogger.warning(
jex.getLocalizedMessage());
}
}
else
{
javax.swing.SwingUtilities.invokeLater(task);
}
});
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Special dispatchers may only be marked as the default
* dispatcher. All other properties are ignored.
*/
private final boolean mSpecial;
/**
* References the method used to dispatch a task. Will be
* {@code null} if {@code mRunQueue} is not {@code null}.
*/
private final Consumer mDispatchHandle;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new dispatcher type for the given run queue
* and handle.
* @param special marks this as a special dispatcher
* which may not be configured.
* @param handle dispatcher method handle. Must be
* {@code null} when {@code handle} is not {@code null}.
*/
private DispatcherType(final boolean special,
final Consumer handle)
{
mSpecial = special;
mDispatchHandle = handle;
} // end of DispatcherType(boolean, Consumer<>)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns {@code true} if this dispatcher type is a
* special, non-configurable dispatcher.
* @return {@code true} if a special dispatcher.
*/
public boolean isSpecial()
{
return (mSpecial);
} // end of isSpecial()
/**
* Returns the task dispatch method handle. May be
* {@code null}.
* @return dispatch method handle.
*/
public Consumer dispatchHandle()
{
return (mDispatchHandle);
} // end of dispatchHandle()
/**
* Returns the {@link DispatcherType} for the given
* {@code name} using case-insensitive string matching.
* If {@code name} is either {@code null} or an empty
* string, then returns {@link DispatcherType#EBUS} by
* default.
* @param name find dispatcher type with the given name
* using case-insensitive matching.
* @return matching dispatcher type; defaults to
* {@link #EBUS}.
*/
public static DispatcherType findType(final String name)
{
final DispatcherType[] types =
DispatcherType.values();
final int numTypes = types.length;
int index;
DispatcherType retval = null;
for (index = 0;
index < numTypes && retval == null;
++index)
{
if (name.equalsIgnoreCase(types[index].name()))
{
retval = types[index];
}
}
if (retval == null)
{
retval = EBUS;
}
return (retval);
} // end of findType(String)
//
// end of Get Methods.
//-------------------------------------------------------
} // end of enum DispatcherType
/**
* When a connection is paused and the server message backlog
* breaches the maximum allowed message backlog limit, this
* policy specifies which messages should be discarded to
* keep the backlog at the limit.
*/
public enum DiscardPolicy
{
/**
* Discard the oldest messages first (FIFO).
*/
OLDEST_FIRST,
/**
* Discard the youngest messages first (LIFO).
*/
YOUNGEST_FIRST
} // end of enum DiscardPolicy
/**
* Defines remote connection side, either acceptor or
* initiator.
*/
public enum ConnectionRole
{
/**
* This side initiated the connection.
*/
INITIATOR,
/**
* This side accepted the connection.
*/
ACCEPTOR
} // end of enum ConnectionRole
/**
* Multicast connections are used to transmit
* notification messages only. As such a multicast
* connection is used either to publish notifications to the
* multicast group or subscribe to notifications from the
* group. Note that a multicast connection cannot both
* publish and subscribe.
*/
public enum MulticastRole
{
/**
* Multicast connection takes eBus notifications
* targeted for remote applications and posts it to the
* multicast group.
*/
PUBLISHER,
/**
* Multicast connection takes eBus notifications from the
* multicast group and publishes it to the local eBus.
*/
SUBSCRIBER
} // end of MulticastRole
/**
* A multi-feed uses either a fixed list of subjects or a
* subject query.
*/
public enum MultifeedType
{
/**
* Fixed subject list.
*/
LIST,
/**
* Subject query pattern.
*/
QUERY
} // end of enum MultifeedType
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Use {@code -D} to set system property {@value} to point to
* the eBus JSON configuration file. eBus will configure
* itself as per the file.
*/
public static final String JSON_FILE_ENV =
"net.sf.eBus.config.jsonFile";
/**
* Generic TLS protocol name is {@value}.
*/
public static final String TLS_PROTOCOL_NAME = "TLS";
/**
* Generic DTLS protocol name is {@value}.
*/
public static final String DTLS_PROTOCOL_NAME = "DTLS";
//
// System property keys.
//
// Property keys.
/**
* All properties keys are prefixed with {@value}.
*/
public static final String EBUS_KEY = "eBus";
/**
* Key {@value} is used to extract the configuration object's
* name.
*/
public static final String NAME_KEY = "name";
/**
* The key {@value} contains a comma-separated list of local
* service names.
*/
public static final String SERVICES_KEY = "services";
/**
* Service keys are prefixed by {@value} and followed by the
* service name.
*/
public static final String SERVICE_PREFIX =
EBUS_KEY + ".service.";
/**
* The key {@value} contains a comma-separated list of remote
* connection names.
*/
public static final String CONNECTIONS_KEY =
"connections";
/**
* Remote connection keys are prefixed by {@value} and
* followed by the connection name as found in
* {@link #CONNECTIONS_KEY}.
*/
public static final String CONNECTION_PREFIX =
EBUS_KEY + ".connection.";
/**
* The key {@value} contains a comma-separated list of
* unique, client dispatcher names.
*/
public static final String DISPATCHERS_KEY =
"dispatchers";
/**
* Dispatcher keys are prefixed by {@value} and followed by
* the dispatcher name as found in {@link #DISPATCHERS_KEY}.
*/
public static final String DISPATCHER_PREFIX =
"dispatcher.";
/**
* Both service and connection definitions use the {@value}
* to define the connection protocol type.
*/
public static final String CONN_TYPE_KEY = "connectionType";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be an integer > zero.
*
*/
public static final String PORT_KEY = "port";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be an integer > zero.
*/
public static final String INBUFFER_SIZE_KEY =
"inputBufferSize";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be an integer > zero.
*/
public static final String OUTBUFFER_SIZE_KEY =
"outputBufferSize";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be either
* {@link ByteOrder#BIG_ENDIAN} or
* {@link ByteOrder#LITTLE_ENDIAN}.
*
* The default is {@link ByteOrder#LITTLE_ENDIAN}.
*
*/
public static final String BYTE_ORDER_KEY =
"byteOrder";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be an integer > zero.
*/
public static final String MSG_QUEUE_SIZE_KEY =
"messageQueueSize";
/**
* The service definition uses the key suffix
* {@value} to define the selector used to monitor the
* service socket. Must be a known selector. If not
* specified, then defaults to
* {@code AsyncChannel.defaultSelector}.
*/
public static final String SVC_SELECTOR_KEY =
"serviceSelector";
/**
* The service definition uses the key suffix
* {@value} to define the selector used to monitor TCP socket
* channels accepted by the service socket. Must be a known
* selector. If not specified, then defaults to
* {@code AsyncChannel.defaultSelector}.
*/
public static final String CONN_SELECTOR_KEY =
"connectionSelector";
/**
* Both the service and connection definitions use the
* {@value} key suffix. Must be a known selector. If not
* specified, then defaults to
* {@code AsyncChannel.defaultSelector}.
*/
public static final String SELECTOR_KEY = "selector";
/**
* The connection definition uses the {@value} key suffix.
* Must be either a valid host name or IP address in dotted
* notation.
*/
public static final String HOST_KEY = "host";
/**
* The connection definition uses the {@value} key suffix.
* Must be an integer ≥ zero.
*/
public static final String BIND_PORT_KEY = "bindPort";
/**
* The connection definition uses the {@value} key suffix.
* Must be a boolean.
*/
public static final String RECONNECT_KEY = "reconnect";
/**
* The connection definition uses the {@value} key suffix.
* Must be an integer ≥ zero.
*/
public static final String RECONNECT_DELAY_KEY =
"reconnectTime";
/**
* The connection definition uses the {@value} key suffix.
* Must be an integer ≥ zero.
*/
public static final String HB_DELAY_KEY = "heartbeatDelay";
/**
* The connection definition uses the {@value} key suffix.
* Must be an integer ≥ zero.
*/
public static final String HB_REPLY_DELAY_KEY =
"heartbeatReplyDelay";
/**
* The service definition uses the {@value} key suffix. Must
* be a valid address filter list.
*/
public static final String FILTER_KEY = "addressFilter";
/**
* The boolean {@value} property is used to specify when this
* dispatcher should be used as the default dispatcher. At
* most one dispatcher should be designated as the default
* dispatcher. If more than one is so designated, the
* subsequent {@code .isDefault} keys are ignored.
*
* If no user-defined dispatcher is marked as default, then
* a {@link ThreadType#BLOCKING blocking},
* {@link Thread#NORM_PRIORITY normal priority} dispatcher
* named {@code EClient.DEFAULT_DISPATCHER} is used as
* the default dispatcher.
*
*
* The default value is {@code false}
* (not the default selector).
*
*/
public static final String DEFAULT_KEY = "isDefault";
/**
* The {@link DispatcherType} {@value} property is used to
* specify the dispatcher thread type. This property allows
* eBus tasks to be executed on supported third-party
* threads.
*
* The default value is {@link DispatcherType#EBUS}.
*
*/
public static final String DISPATCHER_TYPE_KEY =
"dispatcherType";
/**
* The {@link ThreadType run queue} {@value} property is used
* to specify how the dispatcher thread acquires the next
* available {@code EClient} from the run queue by either
* blocking, spinning, spin+park, or spin+yield.
*
* The default value is {@link #DEFAULT_THREAD_TYPE}.
*
*/
public static final String THREAD_TYPE_KEY = "runQueueType";
/**
* If the thread type is
* {@link ThreadType#SPINPARK spin+park} or
* {@link ThreadType#SPINYIELD}, then the
* integer {@code .spinLimit} property may be specified. This
* setting defines the number of times the Dispatcher thread
* may call
* {@link java.util.Queue#poll()} before parking or yielding.
*
* This value must be > zero.
*
*
* Default values is {@link #DEFAULT_SPIN_LIMIT}.
*
*
* This property is ignored is the thread type is not
* spin+park or spin+yield.
*
*/
public static final String SPIN_LIMIT_KEY = "spinLimit";
/**
* If the thread type is
* {@link ThreadType#SPINPARK spin+park}, then the
* integer {@code .parkTime} property may be specified. This
* setting specifies the nanosecond park time taken
* between {@link java.util.Queue#poll()} spin cycles.
*
* This value must be > zero.
*
*
* Default values is {@link #DEFAULT_PARK_TIME}.
*
*
* This property is ignored is the thread type is not
* spin+park.
*
*/
public static final String PARK_TIME_KEY = "parkTime";
/**
* The integer {@value} property is used to set the dispatcher
* thread priority.
*
* The default value is {@link Thread#NORM_PRIORITY}.
*
*/
public static final String PRIORITY_KEY = "priority";
/**
* The key {@value} defines the run quantum each
* client is initially assigned. If a client exhausts its
* quantum, the client is placed back on the run queue.
*/
public static final String QUANTUM_KEY = "quantum";
/**
* The key {@value} defines the classes which are
* posted to this dispatcher. This key is ignored if
* {@link #DEFAULT_KEY .isDefault} is set to {@code true}.
*/
public static final String CLASSES_KEY = "classes";
/**
* The dispatcher definition uses {@value}
* key suffix to specify the number of threads in the
* dispatcher.
*/
public static final String NUM_THREADS_KEY =
"numberThreads";
/**
* If a remote client connection may be paused, then this
* property is defined and set to {@code true}. If not set
* then assumed to be {@code false} and the connection cannot
* be paused. This key may be defined for both acceptor and
* initiator connections.
*/
public static final String CAN_PAUSE_KEY = "canPause";
/**
* If {@link #CAN_PAUSE_KEY} is set to {@code true}, then
* the connection pause parameters are stored under the
* {@value} key. This key may be defined for both acceptor
* and initiator connections.
*/
public static final String PAUSE_KEY = "pause";
/**
* The pause duration is stored in {@value}. This is a
* sub-key under {@link #PAUSE_KEY}. This key may be defined
* for both acceptor and initiator connections.
*/
public static final String PAUSE_DURATION_KEY = "pauseTime";
/**
* Maximum allowed message backlog when paused. This key may
* be defined for both acceptor and initiator connections.
*/
public static final String MAX_BACKLOG_SIZE_KEY =
"maxBacklogSize";
/**
* When the maximum message backlog limit is breached, then
* this policy defines which messages should be discarded.
* This key is defined for initiator connections only.
*/
public static final String DISCARD_POLICY_KEY =
"discardPolicy";
/**
* When a connection is idle (no messages sent or received)
* for a given time, that connection is automatically paused.
* This value is set in the {@value} property.
*/
public static final String IDLE_TIME_KEY = "idleTime";
/**
* Maximum allowed time between pauses, irrespective of
* connection being busy. This value is set in the the
* {@value} property.
*/
public static final String MAX_CONNECT_TIME_KEY =
"maxConnectTime";
/**
* Resumes the client connection automatically when the
* transmit queue reaches this size (application messages
* only). This trigger is turned off when set to zero which
* is the default value. This value should be less than the
* maximum backlog size to be effective.
*/
public static final String RESUME_ON_BACKLOG_SIZE_KEY =
"resumeOnBacklogSize";
/**
* Multicast connections are stored in key {@value}.
*/
public static final String MULTICAST_KEY = "multicast";
/**
* Multicast connection role is stored in key {@value}.
*/
public static final String MULTICAST_ROLE_KEY = "role";
/**
* Multicast group address is stored in key {@value}.
*/
public static final String GROUP_KEY = "group";
/**
* Multicast target port is stored in key {@value}.
*/
public static final String TARGET_PORT_KEY = "targetPort";
/**
* Multicast group network interface is stored in key
* {@value}.
*/
public static final String NET_IF_KEY = " networkInterface";
/**
* Multicast source addresses are stored in key {@value}.
* This property is optional.
*/
public static final String SOURCES_KEY = "sources";
/**
* Multicast group protocol family is stored in key {@value}.
*/
public static final String PROTOCOL_KEY = "protocolFamily";
/**
* Notification multi-feed message keys are stored in key
* {@value}.
*/
public static final String NOTIFICATION_KEY =
"notifications";
/**
* The multi-feed type of {@code LIST} or {@code QUERY} is
* stored in {@value}.
*/
public static final String MULTIFEED_TYPE_KEY =
"multifeedType";
/**
* Multicast message class name is stored in {@value}.
*/
public static final String MESSAGE_CLASS_KEY =
"messageClass";
/**
* Multicast subject query is stored in {@value}.
*/
public static final String SUBJECT_QUERY_KEY =
"subjectQuery";
/**
* Multicast subject list is stored in {@value}.
*/
public static final String SUBJECT_LIST_KEY = "subjectList";
/**
* Multicast query subject is stored in {@value}. This key
* is used only if {@code MULTIFEED_TYPE_KEY} is set to
* {@code QUERY}.
*/
public static final String IS_DYNAMIC_KEY = "isDynamic";
/**
* {@value} key is used for {@code ConfigException} purposes
* only.
*/
private static final String SSL_CONTEXT_KEY = "sslContext";
/**
* {@value} key is used for {@code ThreadAffinityConfigure}
* array. This property is optional.
*/
private static final String AFFINITIES_KEY =
"threadAffinities";
//
// Default values.
//
/**
* Unfortunately, {@link ByteOrder} is not an
* {@code enum}, so we need to define strings for
* comparison purposes.
*/
private static final String BIGENDIAN = "BIG_ENDIAN";
/**
* Unfortunately, {@link ByteOrder} is not an
* {@code enum}, so we need to define strings for
* comparison purposes.
*/
private static final String LITTLEENDIAN = "LITTLE_ENDIAN";
/**
* The default byte order is {@link ByteOrder#LITTLE_ENDIAN}.
*/
private static final ByteOrder DEFAULT_BYTE_ORDER =
ByteOrder.LITTLE_ENDIAN;
/**
* The default Dispatcher thread type is
* {@link ThreadType#BLOCKING}.
*/
public static final String DEFAULT_THREAD_TYPE =
(ThreadType.BLOCKING).textName();
/**
* The default spin limit is {@value} calls to
* {@link java.util.Queue#poll()} before parking or yielding.
*/
public static final int DEFAULT_SPIN_LIMIT = 2_500_000;
/**
* The default park time after spinning is 1 microsecond.
*/
public static final Duration DEFAULT_PARK_TIME =
Duration.ofNanos(1_000L);
/**
* The default dispatcher thread priority is {@value}.
*/
public static final int DEFAULT_PRIORITY =
Thread.NORM_PRIORITY;
/**
* The default run quantum is 500 microseconds.
*/
public static final Duration DEFAULT_QUANTUM =
Duration.ofNanos(500_000L);
/**
* The default dispatcher thread count is 4.
*/
public static final int DEFAULT_NUMBER_THREADS = 4;
/**
* This value specifies that there is no limit to the
* transmit queue size.
*/
public static final int DEFAULT_QUEUE_SIZE = 0;
/**
* By default a lost connection is not reconnected.
*/
public static final boolean DEFAULT_RECONNECT_FLAG = false;
/**
* The default reconnect time is 5 seconds. This value is
* used only if reconnection is set to {@code true} when the
* connection was
* {@code ERemoteApp.openConnection(EConfigure.RemoteConnection) opened}.
*/
public static final long DEFAULT_RECONNECT_TIME = 5000;
/**
* By default heart-beating is turned off.
*/
public static final Duration DEFAULT_HEARTBEAT_DELAY =
Duration.ZERO;
/**
* By default wait indefinitely for a heartbeat reply.
*/
public static final Duration DEFAULT_HEARTBEAT_REPLY_DELAY =
Duration.ZERO;
private static final String INVALID_ADDRESS =
"is not a valid address";
private static final String INVALID_NAME =
"name is null or empty";
private static final String INVALID_DURATION_NULL =
"duration is null";
private static final String INVALID_DURATION =
"duration <= 0";
//-----------------------------------------------------------
// Statics.
//
/**
* Logging subsystem interface.
*/
private static final Logger sLogger =
Logger.getLogger(EConfigure.class.getName());
//-----------------------------------------------------------
// Locals.
//
/**
* Open local services on the given ports. Maps the server
* name to the local service.
*/
private final Map mServices;
/**
* Open connections to the specified remote eBus instances.
* Maps the connection name to the remote connection.
*/
private final Map mRemoteConnections;
/**
* Open connections to the specified multicast groups.
*/
private final Map mMulticastConnections;
/**
* The task dispatcher thread configurations.
*/
private final Map mDispatchers;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates an eBus configuration for the given services and
* remote connections. Each local service and remote
* connection must have a unique name in {@code connections}.
* @param services local eBus services.
* @param conns the remote eBus connections.
* @param mcastconns multicast group connections.
* @param dispatchers dispatcher thread configurations.
*
* @see RemoteConnection
* @see Service
* @see MulticastConnection
* @see Dispatcher
*/
private EConfigure(final Map services,
final Map conns,
final Map mcastconns,
final Map dispatchers)
{
mServices = services;
mRemoteConnections = conns;
mMulticastConnections = mcastconns;
mDispatchers = dispatchers;
} // end of EConfigure(Map<>, Map<>, Map<>, Map<>)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code o} is a non-{@code null}
* {@code EConfigure} instance with the same settings;
* returns {@code false} otherwise.
* @param o Test equals with this object.
* @return {@code true} if {@code o} is a non-{@code null}
* {@code EConfigure} instance with the same settings;
* returns {@code false} otherwise.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof EConfigure)
{
final EConfigure config = (EConfigure) o;
retcode =
(mServices.equals(config.mServices) &&
mRemoteConnections.equals(
config.mRemoteConnections) &&
mDispatchers.equals(config.mDispatchers));
}
return (retcode);
} // end of equals(Object)
/**
* Returns the configuration hash code.
* @return the configuration hash code.
*/
@Override
public int hashCode()
{
return (
Objects.hash(
mServices, mRemoteConnections, mDispatchers));
} // end of hashCode()
/**
* Returns the configuration text representation.
* @return the configuration text representation.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append(" services:");
if (mServices.isEmpty())
{
retval.append(" none\n");
}
else
{
(mServices.values())
.stream()
.forEach(
service ->
retval.append("\n ")
.append(service)
.append('\n'));
}
retval.append(" connections:");
if (mRemoteConnections.isEmpty())
{
retval.append(" none\n");
}
else
{
(mRemoteConnections.values())
.stream()
.forEach(
conn -> retval.append("%n ").append(conn));
}
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Get methods.
//
/**
* Returns {@code true} if at least one service is
* configured and {@code false} otherwise.
* @return {@code true} if there is a service.
*/
public boolean hasServices()
{
return (!mServices.isEmpty());
} // end of hasServices()
/**
* Returns {@code true} if there is an eBus service defined
* with the given name.
* @param name service name.
* @return {@code true} if service exists.
*/
public boolean hasService(final String name)
{
return (mServices.containsKey(name));
} // end of hasService(String)
/**
* Returns the eBus services map.
* @return the eBus services map.
*/
public Map services()
{
return (mServices);
} // end of services()
/**
* Returns the eBus service for the given name. Returns
* {@code null} if there is no service for {@code name}.
* @param name service name.
* @return eBus service with the given name.
*/
public Service service(final String name)
{
return (mServices.get(name));
} // end of service(String)
/**
* Returns {@code true} if remote connections are configured
* and {@code false} if not.
* @return {@code true} if there are remote connections.
*/
public boolean hasRemoteConnections()
{
return (!mRemoteConnections.isEmpty());
} // end of hasRemoteConnections()
/**
* Returns {@code true} if there is a remote connection
* defined with the given name.
* @param name connection name.
* @return {@code true} if named connection exists.
*/
public boolean hasRemoteConnection(final String name)
{
return (mRemoteConnections.containsKey(name));
} // end of hasRemoteConnection(String)
/**
* Returns remote eBus connections map.
* @return remote eBus connections map.
*/
public Map remoteConnections()
{
return (mRemoteConnections);
} // end of remoteConnections()
/**
* Returns remote connection configuration with the given
* name. Returns {@code null} if there is no such named
* remote connection configuration.
* @param name remote connection configuration name.
* @return remote connection configuration instance or
* {@code null} if no such instance exists.
*/
public RemoteConnection connection(final String name)
{
return (mRemoteConnections.get(name));
} // end of connection(String)
/**
* Returns {@code true} if multicast connections are
* configured and {@code false} if not.
* @return {@code true} if there are multicast connections.
*/
public boolean hasMulticastConnections()
{
return (!mMulticastConnections.isEmpty());
} // end of hasMulticastConnections()
/**
* Returns {@code true} if there is a remote connection
* defined with the given name.
* @param name multicast connection name.
* @return {@code true} if named multicast connection exists.
*/
public boolean hasMulticastConnection(final String name)
{
return (mMulticastConnections.containsKey(name));
} // end of hasMulticastConnection(String)
/**
* Returns multicast connections map.
* @return multicast connections map.
*/
public Map multicastConnections()
{
return (mMulticastConnections);
} // end of multicastConnections()
/**
* Returns multicast connection configuration with the given
* name. Returns {@code null} if there is no such named
* multicast connection configuration.
* @param name multicast connection configuration name.
* @return multicast connection configuration instance or
* {@code null} if no such instance exists.
*/
public MulticastConnection multicastConnection(final String name)
{
return (mMulticastConnections.get(name));
} // end of multicastConnection(String)
/**
* Returns {@code true} if dispatchers are configured and
* {@code false} if not.
* @return {@code true} if there are dispatchers configured.
*/
public boolean hasDispatchers()
{
return (!mDispatchers.isEmpty());
} // end of hasDispatchers()
/**
* Returns {@code true} if there is a dispatcher defined
* with the given name.
* @param name dispatcher name.
* @return {@code true} if dispatcher exists.
*/
public boolean hasDispatcher(final String name)
{
return (mDispatchers.containsKey(name));
} // end of hasDispatcher(String)
/**
* Returns the set of client dispatcher configurations.
* @return client dispatcher configurations.
*/
public Map dispatchers()
{
return (mDispatchers);
} // end of dispatchers()
/**
* Returns the dispatcher configuration with the given name.
* Returns {@code null} if there is no such named dispatcher.
* @param name dispatcher configuration name.
* @return dispatcher configuration instance or {@code null}.
*/
public Dispatcher dispatcher(final String name)
{
return (mDispatchers.get(name));
} // end of dispatcher(String)
//
// end of Get methods.
//-----------------------------------------------------------
/**
* Creates an eBus configuration for the given service and
* remote connections. Each remote connection must have a
* unique name in {@code connections}.
* @param services the local eBus services.
* @param connections the remote eBus connections.
* @param mcastConnections multicast connections.
* @param dispatchers dispatcher thread configurations.
* @return an eBus configuration based on the given
* parameters.
*
* @see Service
* @see RemoteConnection
* @see MulticastConnection
* @see Dispatcher
*/
public static EConfigure
create(final Map services,
final Map connections,
final Map mcastConnections,
final Map dispatchers)
{
return (new EConfigure(services,
connections,
mcastConnections,
dispatchers));
} // end of load(Map<>, Map<>, Map<>, Map<>)
/**
* Returns a new {@code ServerBuilder} instance used to
* construct an {@code EServer} programmatically.
* @return new {@code ServerBuilder} instance.
*/
public static ServerBuilder serverBuilder()
{
return (new ServerBuilder());
} // end of serverBuilder()
/**
* Returns a new {@link ConnectionBuilder} instance used to
* construct a {@code ERemoteApp.RemoteConnection}
* programmatically.
* @return new {@code ConnectionBuilder} instance.
*/
public static ConnectionBuilder connectionBuilder()
{
return (new ConnectionBuilder());
} // end of connectionBuilder()
/**
* Returns a new {@link MulticastBuilder} instance used to
* construct a {@code EMcastConnection} programatically.
* @return new {@code MulticastBuilder} instance.
*/
public static MulticastBuilder multicastBuilder()
{
return (new MulticastBuilder());
} // end of multicastBuilder()
/**
* Returns a new {@link DispatcherBuilder} instance used to
* construct a {@link Dispatcher}.
* @return new {@code DispatcherBuilder} instance.
*/
public static DispatcherBuilder dispatcherBuilder()
{
return (new DispatcherBuilder());
} // end of dispatcherBuilder()
/**
* Returns a new {@link PauseBuilder} instance used to
* construct a {@link PauseConfig}.
* @param role build pause configuration for this connection
* role.
* @return new {@link PauseBuilder} instance.
*/
public static PauseBuilder pauseBuilder(final ConnectionRole role)
{
return (new PauseBuilder(role));
} // end of pauseBuilder(ConnectionRole)
/**
* Returns a new {@link McastNotifyBuilder} instance used to
* construct a {@link McastNotifyConfig}.
* @return multicast notification builder.
*/
public static McastNotifyBuilder notificationBuilder()
{
return (new McastNotifyBuilder());
} // end of notifcationBuilder()
/**
* Returns the eBus service, remote connection, and
* dispatcher configurations found in the given JSON
* configuration.
*
* An example of the eBus JSON configuration:
*
*
* Note: Each name must be unique within the selectors,
* services, and connections lists.
*
* "selectors" : [
{
"name" : "mdSelector"
"type" : "spinning"
"isDefault" : "false"
"priority" : 9
},
{
"name" : "stockSelector"
"type" : "spin+park"
"isDefault" : "false"
"priority" : 6
"spinLimit" : 3000000
"parkTime" : 1000
}
]
"services" : [
{
"name" : "stockService"
"port" : 12345
"addressFilter" : [
"198.168.3.2",
"198.168.3.3:55001"
]
"serviceSelector" : "stockSelector"
"connectionSelector" : "stockSelector"
"byteOrder" : "BIG_ENDIAN"
"inputBufferSize" : 8192
"outputBufferSize" : 65536
"messageQueueSize" : 100
"heartbeatDelay" : 30000
"heartbeatReply" : 500
"canPause" : false
}
]
"connections" : [
{
"name" : "marketData"
"host" : "192.168.3.4"
"port" : 10000
"inputBufferSize" : 65536
"outputBufferSize" : 8192
"byteOrder" : "BIG_ENDIAN"
"messageQueueSize" : 0
"selector" : "mdSelector"
"reconnect" : true
"reconnectTime" : 500
"canPause" : false
},
{
"name" : "stockOrders"
"host" : "StocksRUs.com"
"port" : 10001
"bindPort" : 55000
"inputBufferSize" : 8192
"outputBufferSize" : 65536
"byteOrder" : "BIG_ENDIAN"
"messageQueueSize" : 10
"selector" : "stockSelector"
"reconnect" : true
"reconnectTime" : 500
"canPause" : false
}
]
* @param config extract the eBus configuration from these
* JSON properties.
* @return eBus configuration settings.
* @throws ConfigException
* if {@code config} contains an invalid or incomplete eBus
* configuration.
*/
public static EConfigure load(final Config config)
{
return (new EConfigure(loadServices(config),
loadConnections(config),
loadMulticastConnections(config),
loadDispatchers(config)));
} // end of load(Config)
/**
* Returns the eBus TCP services. Returns an empty map if
* there are no eBus services.
* @param config {@code typesafe.config} HOCON configuration
* containing service definition.
* @return eBus service.
* @throws ConfigException
* if a service definition is invalid.
*
* @see #load(Config)
*/
public static Map loadServices(final Config config)
{
Service service;
final Map retval = new HashMap<>();
// Are there any defined services?
if (config.hasPath(SERVICES_KEY))
{
// Yes. Load each service in turn.
for (ConfigObject co :
config.getObjectList(SERVICES_KEY))
{
service = loadService(co.toConfig());
retval.put(service.name(), service);
}
}
// Else return an empty hash map.
return (retval);
} // end of loadServices(Config)
/**
* Returns remote eBus connections found in {@code config}.
* If there are no remote connections then returns an empty
* map.
* @param config {@code typesafe.config} HOCON configuration
* containing connection definitions.
* @return remote eBus connections map.
* @throws ConfigException
* if a remote connection definition is invalid.
*
* @see #load(Config)
*/
public static Map loadConnections(final Config config)
{
final Map retval =
new HashMap<>();
// Are there any connections to load?
if (config.hasPath(CONNECTIONS_KEY))
{
RemoteConnection connection;
// Yes. Load each connection in turn.
for (ConfigObject co : config.getObjectList(CONNECTIONS_KEY))
{
connection = loadConnection(co.toConfig());
retval.put(connection.name(), connection);
}
}
return (retval);
} // end of loadConnections(Config)
/**
* Returns multicast connections found in {@code config}. If
* there are no multicast connections then returns an empty
* map.
* @param config {@code typesafe.config} HOCON configuration
* containing multicast connection definitions.
* @return multicast connections map.
* @throws ConfigException
* if an invalid multicast connection definition is found.
*/
public static Map loadMulticastConnections(final Config config)
{
final Map retval =
new HashMap<>();
// Are there any multicast definitions to load?
if (config.hasPath(MULTICAST_KEY))
{
MulticastConnection mcastConnection;
// Yes. Load each multicast connection in turn.
for (ConfigObject co : config.getObjectList(MULTICAST_KEY))
{
mcastConnection =
loadMulticastConnection(co.toConfig());
retval.put(
mcastConnection.name(), mcastConnection);
}
}
return (retval);
} // end of loadMulticastConnections(Config)
/**
* Returns the eBus dispatcher configuration found in the
* given JSON configuration. If this is no dispatcher
* configuration found, then returns an empty map.
* @param config JSON configuration.
* @return map from dispatcher name to its configuration.
* @throws ConfigException
* if {@code config} contains an invalid dispatcher
* configuration.
*/
public static Map loadDispatchers(final Config config)
{
Dispatcher dispatcher;
final Map retval = new HashMap<>();
// Are there any dispatchers to load?
if (config.hasPath(DISPATCHERS_KEY))
{
// Yes. Load each dispatcher in turn.
for (ConfigObject co : config.getObjectList(DISPATCHERS_KEY))
{
dispatcher = loadDispatcher(co.toConfig());
retval.put(dispatcher.name(), dispatcher);
}
}
return (retval);
} // end of loadDispatchers(Config)
/**
* Returns a {@link Service} instance based on the given
* JSON configuration.
* @param config JSON configuration.
* @return eBus service instance.
* @throws ConfigException
* if {@code config} is either missing required settings or
* existing settings are invalid.
*/
private static Service loadService(final Config config)
{
final ConnectionType connType =
(config.hasPath(CONN_TYPE_KEY) ?
config.getEnum(ConnectionType.class, CONN_TYPE_KEY) :
ConnectionType.TCP);
final ServerBuilder builder = new ServerBuilder();
builder.loaderFlag(true)
.name(config.getString(NAME_KEY))
.connectionType(connType)
.port(config.getInt(PORT_KEY))
.addressFilter(
AddressFilter.load(config, FILTER_KEY))
.inputBufferSize(
config.hasPath(INBUFFER_SIZE_KEY) ?
config.getInt(INBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.outputBufferSize(
config.hasPath(OUTBUFFER_SIZE_KEY) ?
config.getInt(OUTBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.byteOrder(loadByteOrder(config, BYTE_ORDER_KEY))
.messageQueueSize(
config.hasPath(MSG_QUEUE_SIZE_KEY) ?
config.getInt(MSG_QUEUE_SIZE_KEY) :
DEFAULT_QUEUE_SIZE)
.serviceSelector(
config.hasPath(SVC_SELECTOR_KEY) ?
config.getString(SVC_SELECTOR_KEY) :
(ENetConfigure.defaultSelector()).name())
.connectionSelector(
config.hasPath(CONN_SELECTOR_KEY) ?
config.getString(CONN_SELECTOR_KEY) :
(ENetConfigure.defaultSelector()).name())
.heartbeatDelay(
config.hasPath(HB_DELAY_KEY) ?
config.getDuration(HB_DELAY_KEY) :
Duration.ZERO)
.heartbeatReplyDelay(
config.hasPath(HB_REPLY_DELAY_KEY) ?
config.getDuration(HB_REPLY_DELAY_KEY) :
Duration.ZERO)
.canPause(config.hasPath(CAN_PAUSE_KEY) &&
config.getBoolean(CAN_PAUSE_KEY));
if (builder.mCanPause)
{
// Yes. Load the pause-specific settings.
final Config pauseConfig =
config.getConfig(PAUSE_KEY);
final PauseBuilder pauseBuilder =
new PauseBuilder(ConnectionRole.ACCEPTOR);
pauseBuilder.duration(pauseConfig.getDuration(PAUSE_DURATION_KEY))
.maxBacklogSize(pauseConfig.getInt(MAX_BACKLOG_SIZE_KEY));
builder.pauseConfig(pauseBuilder.build());
}
return (builder.build());
} // end of loadService(Config)
/**
* Returns a {@link RemoteConnection} instance based on the
* given JSON configuration.
* @param config extract connection from this JSON
* configuration.
* @return remote connection configuration.
* @throws ConfigException
* if {@code config} contains an invalid connection
* configuration.
*/
private static RemoteConnection loadConnection(final Config config)
{
final ConnectionType connType =
(config.hasPath(CONN_TYPE_KEY) ?
config.getEnum(ConnectionType.class, CONN_TYPE_KEY) :
ConnectionType.TCP);
final ConnectionBuilder builder =
new ConnectionBuilder();
// Only plain text TCP connections are supported
// because supporting a secure TCP connection
// requires storing the passphrase in config.
// This can be done in a somewhat safe manner but
// there are still other security issues.
builder.loaderFlag(true)
.name(config.getString(NAME_KEY))
.connectionType(connType)
.address(loadAddress(config))
.bindPort(
config.hasPath(BIND_PORT_KEY) ?
config.getInt(BIND_PORT_KEY) :
ANY_PORT)
.inputBufferSize(
config.hasPath(INBUFFER_SIZE_KEY) ?
config.getInt(INBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.outputBufferSize(
config.hasPath(OUTBUFFER_SIZE_KEY) ?
config.getInt(OUTBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.byteOrder(loadByteOrder(config, BYTE_ORDER_KEY))
.messageQueueSize(
config.hasPath(MSG_QUEUE_SIZE_KEY) ?
config.getInt(MSG_QUEUE_SIZE_KEY) :
DEFAULT_QUEUE_SIZE)
.selector(
config.hasPath(SELECTOR_KEY) ?
config.getString(SELECTOR_KEY) :
(ENetConfigure.defaultSelector()).name())
.reconnect(config.hasPath(RECONNECT_KEY) &&
config.getBoolean(RECONNECT_KEY))
.reconnectDelay(
builder.mReconnectFlag ?
config.getDuration(RECONNECT_DELAY_KEY) :
Duration.ZERO)
.heartbeatDelay(
config.hasPath(HB_DELAY_KEY) ?
config.getDuration(HB_DELAY_KEY) :
DEFAULT_HEARTBEAT_DELAY)
.heartbeatReplyDelay(
config.hasPath(HB_REPLY_DELAY_KEY) ?
config.getDuration(HB_REPLY_DELAY_KEY) :
DEFAULT_HEARTBEAT_REPLY_DELAY)
.canPause(config.hasPath(CAN_PAUSE_KEY) &&
config.getBoolean(CAN_PAUSE_KEY));
if (builder.mCanPause)
{
final Config pauseConfig =
config.getConfig(PAUSE_KEY);
final PauseBuilder pauseBuilder =
new PauseBuilder(ConnectionRole.INITIATOR);
pauseBuilder.duration(pauseConfig.getDuration(PAUSE_DURATION_KEY))
.maxBacklogSize(pauseConfig.getInt(MAX_BACKLOG_SIZE_KEY))
.discardPolicy(pauseConfig.getEnum(DiscardPolicy.class, DISCARD_POLICY_KEY))
.idleTime(pauseConfig.getDuration(IDLE_TIME_KEY))
.maxConnectionTime(pauseConfig.getDuration(MAX_CONNECT_TIME_KEY));
if (pauseConfig.hasPath(RESUME_ON_BACKLOG_SIZE_KEY))
{
pauseBuilder.resumeOnBacklogSize(pauseConfig.getInt(RESUME_ON_BACKLOG_SIZE_KEY));
}
builder.pauseConfig(pauseBuilder.build());
}
return (builder.build());
} // end of loadConnection(Config)
/**
* Returns a {@link MulticastConnection} instance based on
* the given JSON configuration.
* @param config extract multicast connection from this JSON
* configuration.
* @return multicast connection configuration.
* @throws ConfigException
* if {@code config} contains an invalid connection multicast
* configuration.
*/
private static MulticastConnection loadMulticastConnection(final Config config)
{
final NetworkInterface netIf =
loadNetworkInterface(config);
final List notifications =
loadMcastNotifications(config);
final MulticastBuilder builder = new MulticastBuilder();
builder.name(config.getString(NAME_KEY))
.role(config.getEnum(MulticastRole.class,
MULTICAST_ROLE_KEY))
.group(parseAddress(GROUP_KEY, config))
.targetPort(config.getInt(TARGET_PORT_KEY))
.networkInterface(netIf)
.sources(loadSources(config))
.bindPort(config.hasPath(BIND_PORT_KEY) ?
config.getInt(BIND_PORT_KEY) :
ANY_PORT)
.protocolFamily(
config.getEnum(StandardProtocolFamily.class,
PROTOCOL_KEY))
.byteOrder(loadByteOrder(config, BYTE_ORDER_KEY))
.selector(
config.hasPath(SELECTOR_KEY) ?
config.getString(SELECTOR_KEY) :
(ENetConfigure.defaultSelector()).name())
.inputBufferSize(
config.hasPath(INBUFFER_SIZE_KEY) ?
config.getInt(INBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.outputBufferSize(
config.hasPath(OUTBUFFER_SIZE_KEY) ?
config.getInt(OUTBUFFER_SIZE_KEY) :
DEFAULT_BUFFER_SIZE)
.notifications(notifications);
return (builder.build());
} // end of loadMulticastConnection(Config)
/**
* Returns a dispatcher configured based on the given JSON
* properties.
* @param config JSCON configuration.
* @return dispatcher
* @throws ConfigException
* if {@code config} contains an invalid dispatcher
* configuration.
*/
@VisibleForTesting
public static Dispatcher loadDispatcher(final Config config)
{
DispatcherBuilder builder;
builder = new DispatcherBuilder();
builder.name(config.getString(NAME_KEY))
.dispatcherType(
DispatcherType.findType(builder.mName));
// If this dispatcher type is special, then only
// certain properties apply.
if ((builder.mType).isSpecial())
{
loadSpecialDispatcher(builder, config);
}
else
{
loadDispatcher(builder, config);
}
return (builder.build());
} // end of loadDispatcher(Config)
/**
* Returns the named {@link DispatcherType#EBUS eBus}
* dispatcher loaded in from the given JSON configuration.
* @param builder place dispatcher configuration into this
* builder.
* @param config JSON configuration.
* @throws ConfigException
* if {@code config} contains an invalid dispatcher
* configuration.
*/
@SuppressWarnings ("fallthrough")
private static void loadDispatcher(final DispatcherBuilder builder,
final Config config)
throws ConfigException
{
builder.threadType(config.hasPath(THREAD_TYPE_KEY) ?
ThreadType.find(config.getString(THREAD_TYPE_KEY)) :
ThreadType.find(DEFAULT_THREAD_TYPE));
switch (builder.mRunQueueType)
{
case SPINPARK:
builder.parkTime(config.hasPath(PARK_TIME_KEY) ?
config.getDuration(PARK_TIME_KEY) :
DEFAULT_PARK_TIME);
// Continue on through to the next case.
case SPINYIELD:
builder.spinLimit(config.hasPath(SPIN_LIMIT_KEY) ?
config.getLong(SPIN_LIMIT_KEY) :
DEFAULT_SPIN_LIMIT);
break;
default:
// Do nothing for the remaining cases.
}
builder.priority(config.hasPath(PRIORITY_KEY) ?
config.getInt(PRIORITY_KEY) :
DEFAULT_PRIORITY)
.quantum(config.hasPath(QUANTUM_KEY) ?
config.getDuration(QUANTUM_KEY) :
DEFAULT_QUANTUM)
.numberThreads(config.hasPath(NUM_THREADS_KEY) ?
config.getInt(NUM_THREADS_KEY) :
DEFAULT_NUMBER_THREADS)
.isDefault(config.hasPath(DEFAULT_KEY) &&
config.getBoolean(DEFAULT_KEY))
.classes(builder.mIsDefault ?
new Class>[0] :
loadClasses(config))
.threadAffinity(
ThreadAffinityConfigure.loadAffinities(
AFFINITIES_KEY, config));
} // end of loadDispatcher(DipatcherBuilder, Config)
/**
* Returns the named special dispatcher loaded from the given
* configuration. Special dispatchers only use the
* "isDefault" and "classes" keys.
* @param builder place special dispatcher configuration into
* this builder.
* @param config JSON configuration containing special
* dispatcher configuration.
* @throws ConfigException
* if {@code config} contains an invalid special dispatcher
* configuration.
*/
// This method is called from loadDispatcher(Config)
@SuppressWarnings({"java:S1125", "java:S1144"})
private static void loadSpecialDispatcher(final DispatcherBuilder builder,
final Config config)
throws ConfigException
{
builder.isDefault(config.hasPath(DEFAULT_KEY) ?
config.getBoolean(DEFAULT_KEY) :
false)
.classes(builder.mIsDefault ?
new Class>[0] :
loadClasses(config))
// Set the priority, quantum, and numThreads to
// zero since special Dispatcher threads are 3rd
// party and not configured by eBus. Uses a
// blocking thread type.
.threadType(ThreadType.BLOCKING)
.spinLimit(0L)
.parkTime(Duration.ZERO)
.priority(0)
.quantum(Duration.ZERO)
.numberThreads(0);
} // end of loadSpecialDispatcher(DispatcherBuilder, Config)
private static InetSocketAddress loadAddress(final Config config)
{
final String host = config.getString(HOST_KEY);
final int port = config.getInt(PORT_KEY);
final InetSocketAddress retval;
try
{
retval =
new InetSocketAddress(
InetAddress.getByName(host), port);
}
catch (UnknownHostException hostex)
{
throw (
new ConfigException.BadValue(
HOST_KEY,
"\"" + host + "\" " + INVALID_ADDRESS,
hostex));
}
return (retval);
} // end of loadAddress(Config)
private static InetAddress parseAddress(final String key,
final Config config)
{
final String s = config.getString(key);
InetAddress retval = null;
if (!Strings.isNullOrEmpty(s))
{
try
{
retval = InetAddress.getByName(s);
}
catch (UnknownHostException hostex)
{
throw (
new ConfigException.BadValue(
key,
"\"" + s + "\" " + INVALID_ADDRESS,
hostex));
}
}
return (retval);
} // end of parseAddress(String, String)
private static NetworkInterface loadNetworkInterface(final Config config)
{
final String s = config.getString(NET_IF_KEY);
final NetworkInterface retval;
try
{
retval = NetworkInterface.getByName(s);
}
catch (SocketException sockex)
{
throw (
new ConfigException.BadValue(
NET_IF_KEY,
"\"" + s + "\" is not a valid network interface",
sockex));
}
return (retval);
} // end of loadNetworkInterface(Config)
/**
* Returns the buffer byte order stored in the given key path
* in {@code config}. If not defined in {@code config}, then
* returns {@link #DEFAULT_BYTE_ORDER}.
* @param config JSON configuration containing byte order
* {@code key}.
* @param key JSON configuration path key.
* @return byte order
* @throws ConfigException
* if {@code key} contains an invalid byte order.
*/
private static ByteOrder loadByteOrder(final Config config,
final String key)
throws ConfigException
{
final ByteOrder retval;
if (!config.hasPathOrNull(BYTE_ORDER_KEY))
{
retval = DEFAULT_BYTE_ORDER;
}
else
{
final String value = config.getString(key);
switch (value)
{
case BIGENDIAN:
retval = ByteOrder.BIG_ENDIAN;
break;
case LITTLEENDIAN:
retval = ByteOrder.LITTLE_ENDIAN;
break;
default:
throw (
new ConfigException.BadValue(
key,
"\"" + value + "\" is not a valid java.nio.ByteOrder"));
}
}
return (retval);
} // end of loadByteOrder(Config, String)
/**
* Returns the classes listed in the "classes" Dispatcher
* key.
* @param config JSON configuration containing the classes
* list.
* @return classes array.
* @throws ConfigException
* if {@code config} contains an invalid class name.
*/
private static Class>[] loadClasses(final Config config)
{
final List classNames =
config.getStringList(CLASSES_KEY);
int index = 0;
final Class>[] retval = new Class[classNames.size()];
// If no classes were provided, then throw an
// exception.
if (classNames.isEmpty())
{
throw (
new ConfigException.BadValue(
CLASSES_KEY, "classes are missing or empty"));
}
for (String className : classNames)
{
try
{
retval[index] = Class.forName(className);
}
catch (ClassNotFoundException classex)
{
throw (
new ConfigException.BadValue(
CLASSES_KEY,
"\"" + className + "\" is an unknown class",
classex));
}
++index;
}
return (retval);
} // end of loadClasses(Config)
/**
* Returns immutable list of source addresses loaded from
* given typesafe configuration. If no source addresses are
* configured then returns a non-{@code null}, empty list.
* @param config extract source addresses from this
* configuration.
* @return immutable source address list.
*/
private static List loadSources(final Config config)
{
final ImmutableList.Builder builder =
ImmutableList.builder();
if (config.hasPath(SOURCES_KEY))
{
config.getStringList(SOURCES_KEY)
.forEach(
host ->
{
try
{
builder.add(
InetAddress.getByName(host));
}
catch (UnknownHostException hostex)
{
throw (
new ConfigException.BadValue(
SOURCES_KEY,
"\"" + host + "\" " + INVALID_ADDRESS,
hostex));
}
});
}
return (builder.build());
} // end of loadSources(Config)
private static List loadMcastNotifications(final Config config)
{
// Are any notification keys defined?
if (!config.hasPath(NOTIFICATION_KEY))
{
// No. There must be at least one notification
// defined.
throw (
new ConfigException.Generic(
NOTIFICATION_KEY + " property is missing"));
}
final ImmutableList.Builder builder =
ImmutableList.builder();
config.getObjectList(NOTIFICATION_KEY)
.forEach(
co ->
builder.add(
loadNotification(co.toConfig())));
return (builder.build());
} // end of loadMcastNotifications(Config)
private static McastNotifyConfig loadNotification(final Config config)
{
final MultifeedType type =
config.getEnum(
MultifeedType.class, MULTIFEED_TYPE_KEY);
final McastNotifyBuilder builder =
new McastNotifyBuilder();
builder.feedType(type)
.messageClass(config.getString(MESSAGE_CLASS_KEY));
if (type == MultifeedType.LIST)
{
builder.subjectList(config.getStringList(SUBJECT_LIST_KEY));
}
else
{
builder.subjectQuery(config.getString(SUBJECT_QUERY_KEY))
.isDynamic(config.getBoolean(IS_DYNAMIC_KEY));
}
return (builder.build());
} // eof loadNotification(Config)
//---------------------------------------------------------------
// Inner classes.
//
/**
* Base class for service and remote connections. Contains
* those data members common to both.
*/
public abstract static class AbstractConfig
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* eBus local name.
*/
protected final String mName;
/**
* The connections are of this type.
*/
protected final ConnectionType mConnectionType;
/**
* Input buffer size.
*/
protected final int mInputBufferSize;
/**
* Output buffer size.
*/
protected final int mOutputBufferSize;
/**
* The input and output buffer use this byte ordering.
*/
protected final ByteOrder mByteOrder;
/**
* Maximum message queue size.
*/
protected final int mMsgQueueSize;
/**
* Send a heartbeat this many milliseconds after the
* last received input. A zero value means heartbeating
* is not performed.
*/
protected final Duration mHbDelay;
/**
* Wait this many milliseconds for a heartbeat reply.
* This timer is reset when a non-heartbeat reply is
* received. A zero value means an indefinite reply
* time.
*/
protected final Duration mHbReplyDelay;
/**
* If {@link #mConnectionType} is
* {@link ConnectionType#SECURE_TCP}, then this is the
* SSL context used to secure the connection.
*/
protected final SSLContext mSSLContext;
/**
* Set to {@code true} if accepted connections may be
* paused.
*/
protected final boolean mCanPause;
/**
* If {@link #mCanPause} is {@code true}, then the pause
* configuration is defined. Otherwise set to
* {@code null}.
*/
protected final PauseConfig mPauseConfig;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Constructs the common information for service and
* remote connections.
* @param builder connection builder.
*/
protected AbstractConfig(final AbstractBuilder> builder)
{
this.mName = builder.mName;
this.mConnectionType = builder.mConnectionType;
this.mInputBufferSize = builder.mInputBufferSize;
this.mOutputBufferSize = builder.mOutputBufferSize;
this.mByteOrder = builder.mByteOrder;
this.mMsgQueueSize = builder.mMsgQueueSize;
this.mHbDelay = builder.mHbDelay;
this.mHbReplyDelay = builder.mHbReplyDelay;
this.mSSLContext = builder.mSSLContext;
this.mCanPause = builder.mCanPause;
this.mPauseConfig = builder.mPauseConfig;
} // end of AbstractConfig()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the remote connection unique name.
* @return the remote connection unique name.
*/
public final String name()
{
return (mName);
} // end of name()
/**
* Returns the channel type. This is currently limited to
* plain text TCP and SSL/TLS secure TCP connections.
* @return connection type.
*/
public final ConnectionType connectionType()
{
return (mConnectionType);
} // end of connectionType()
/**
* Returns the connection input buffer size.
* @return the connection input buffer size.
*/
public final int inputBufferSize()
{
return (mInputBufferSize);
} // end of inputBufferSize()
/**
* Returns the connection output buffer size.
* @return the connection output buffer size.
*/
public final int outputBufferSize()
{
return (mOutputBufferSize);
} // end of outputBufferSize()
/**
* Returns connection buffer byte ordering.
* @return the connection buffer byte ordering.
*/
public final ByteOrder byteOrder()
{
return (mByteOrder);
} // end of byteOrder()
/**
* Returns the maximum number of messages on the
* output queue. Returns zero if there is no limit.
* @return the message queue maximum size.
*/
public final int messageQueueSize()
{
return (mMsgQueueSize);
} // end of messageQueueSize()
/**
* Returns the heartbeat delay in milliseconds. A zero
* value means that heartbeating is not performed.
* @return the heartbeat delay in milliseconds.
*/
public final Duration heartbeatDelay()
{
return (mHbDelay);
} // end of heartbeatDelay()
/**
* Returns the heartbeat reply delay. A zero value means
* an indefinite delay.
* @return the heartbeat reply delay.
*/
public final Duration heartbeatReplyDelay()
{
return (mHbReplyDelay);
} // end of heartbeatReplyDelay()
/**
* Returns the SSL context used for a secure connection
* type. Returns {@code null} if connection type is
* not {@link ConnectionType#SECURE_TCP}.
* @return SSL context. May return {@code null}.
*/
public final SSLContext sslContext()
{
return (mSSLContext);
} // end of sslContext()
/**
* Returns {@code true} if pause requests are accepted
* and {@code false} if not.
* @return {@code true} if accepted connections may be
* paused.
*/
public final boolean canPause()
{
return (mCanPause);
} // end of canPause()
/**
* Returns the connection pause configuration. If
* {@link #canPause()} returns {@code false}, then
* returns {@code null}.
* @return connection pause configuration.
*/
public final PauseConfig pauseConfiguration()
{
return (mPauseConfig);
} // end of pauseConfiguration()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class AbstractConfig
/**
* This immutable class stores the configuration for an
* eBus service. The service properties are:
*
* Service JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code name}
* Yes
* {@code String}
* NA
*
* Unique service name. Duplicate names with the
* services array are not allowed.
*
*
*
* {@code connectionType}
* No
* {@link ConnectionType}
* {@link ConnectionType#TCP}
*
* Server and accepted connection type.
* {@link ConnectionType#SECURE_TCP} and
* {@link ConnectionType#SECURE_UDP} are not
* allowed when defined in a configuration file since
* this requires placing sensitive information in the
* clear in this file.
*
*
*
* {@code port}
* Yes
* {@code int} > zero and < 65,536
* NA
*
* Server socket port.
*
*
*
* {@code addressFilter}
* No
* {@link AddressFilter}
* {@code null}
*
* Optional address filter used to decide if a remote
* eBus is allowed to connect to this eBus service.
*
*
*
* {@code inputBufferSize}
* No
* {@code int}
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Accepted socket input buffer size. Limits number of
* bytes received at one time.
*
*
*
* {@code outputBufferSize}
* No
* {@code int}
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Accepted socket output buffer size. Limits number of
* bytes transmitted at one time.
*
*
*
* {@code byteOrder}
* No
* {@link ByteOrder}
* {@link EConfigure#DEFAULT_BYTE_ORDER}
*
* Accepted socket encodes/decodes eBus messages using
* this byte ordering.
*
*
*
* {@code messageQueueSize}
* No
* {@code int}
* {@link EConfigure#DEFAULT_QUEUE_SIZE}
*
* Accepted socket outbound message queue size. A zero
* queue size means the queue size is unlimited. Used
* only for TCP connection types.
*
*
*
* {@code serviceSelector}
* No
* {@code String}
* Default eBus selector thread
*
* Service socket is monitored by this selector thread.
*
*
*
* {@code connectionSelector}
* No
* {@code String}
* Default eBus selector thread
*
* Accepted sockets are monitored by this selector
* thread.
*
*
*
* {@code heartbeatDelay}
* No
*
* {@code int} followed by time unit. Example:
* {@code 30 seconds}
*
* Zero (no heartbeating}
*
* Accepted socket heartbeat rate. A zero heartbeat
* rate means accepted sockets do not send heartbeat
* messages to peer.
*
*
*
* {@code heartbeatReplyDelay}
* No
*
* {@code int} followed by time unit. Example:
* {@code 500 milliseconds}.
*
* Zero (wait indefinitely for reply)
*
* Accepted sockets wait this long for reply to
* heartbeat message. A zero heartbeat reply delay
* means wait indefinitely. This setting is ignored in
* heartbeat rate is zero.
*
*
*
* {@code canPause}
* No
* {@code boolean}
* {@code false}
*
* If {@code true} accepted sockets support pause
* requests from peer according to {@link PauseConfig}
* settings.
*
*
*
*
* Example service configuration.
*
* name : service1
port : 12345
addressFilter : [
"127.0.0.1",
"127.0.0.1:54321"
]
serviceSelector : selector1
connectionSelector : selector1
byteOrder : LITTLE_ENDIAN
inputBufferSize : 8192
outputBufferSize : 65536
messageQueueSize : 100
canPause : false
*
* @see RemoteConnection
* @see PauseConfig
*/
public static final class Service
extends AbstractConfig
implements Comparable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* TCP port.
*/
private final int mPort;
/**
* The optional address filter.
*/
private final AddressFilter mAddressFilter;
/**
* Register this service with this specified selector.
*/
private final String mServiceSelector;
/**
* Register accepted connections with this specified
* selector.
*/
private final String mConnSelector;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates an eBus service configuration from the
* builder's current settings.
* @param builder construct service configuration from
* these settings.
*/
/* package */ Service(final ServerBuilder builder)
{
super (builder);
this.mPort = builder.mPort;
this.mAddressFilter = builder.mAddressFilter;
this.mServiceSelector = builder.mServiceSelector;
this.mConnSelector = builder.mConnSelector;
} // end of Service(ServerBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Comparable Interface Implementation.
//
/**
* Compares this service configuration with
* {@code service} based on the service ports.
* @param service compare this service instance.
* @return an integer value.
*/
@Override
public int compareTo(
final Service service)
{
return (mPort - service.mPort);
} // end of compareTo(Service)
//
// end of Comparable Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code o} is a {@code Service}
* instance with the same port and {@code false}
* otherwise.
* @param o Test if this object is equal.
* @return {@code true} if {@code o} is a {@code Service}
* instance with the same port and {@code false}
* otherwise.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof Service)
{
final Service svc = (Service) o;
retcode =
(mConnectionType == svc.mConnectionType &&
mPort == svc.mPort);
}
return (retcode);
} // end of equals(Object)
/**
* Returns the service port.
* @return the service port.
*/
@Override
public int hashCode()
{
return (Objects.hash(mConnectionType, mPort));
} // end of hashCode()
/**
* Returns a text representation of this eBus service.
* @return a text representation of this eBus service.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append('[')
.append(mConnectionType)
.append(' ')
.append(mPort)
.append("]\n")
.append(" address filter: ")
.append(mAddressFilter)
.append("\n socket input size: ")
.append(mInputBufferSize)
.append("\nsocket output size: ")
.append(mOutputBufferSize)
.append("\n socket byte order: ")
.append(mByteOrder)
.append("\n max queue size: ")
.append(mMsgQueueSize)
.append("\n selector: ")
.append(mConnSelector);
if (mConnectionType == ConnectionType.SECURE_TCP)
{
retval.append("\n SSL context: ")
.append(mSSLContext);
}
retval.append("\n can pause: ")
.append(mCanPause);
if (mCanPause)
{
retval.append("\n pause config: ")
.append(mPauseConfig);
}
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the service port.
* @return the service port.
*/
public int port()
{
return (mPort);
} // end of port()
/**
* Returns the address filter. May return {@code nul}.
* @return the address filter.
*/
public AddressFilter addressFilter()
{
return (mAddressFilter);
} // end of addressFilter()
/**
* Returns the selector information to which the service
* socket channel is registered.
* @return service socket channel selector.
*/
public String serviceSelector()
{
return (mServiceSelector);
} // end of serviceSelector()
/**
* Returns the selector information to which accepted
* connections are registered.
* @return accepted connection selector information.
*/
public String connectionSelector()
{
return (mConnSelector);
} // end of connectionSelector()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of Service
/**
* This immutable class stores the information pertaining
* to a remote eBus connection. The remote connection JSON
* properties are:
*
* Remote Connection JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code name}
* Yes
* {@code String}
* NA
*
* Unique remote connection name. Duplicate names within
* the connections array are not allowd.
*
*
*
* {@code connectionType}
* No
* {@link ConnectionType}
* {@link ConnectionType#TCP}
*
* Protocol used to establish connection to remote eBus
* peer. {@link ConnectionType#SECURE_TCP} and
* {@link ConnectionType#SECURE_UDP} are not
* allowed when defined in a configuration file since this
* requires placing sensitive information in the clear in
* this file.
*
*
*
* {@code host}
* Yes
* {@code String}
* NA
*
* eBus peer host specified either as a name or in
* dotted notation.
*
*
*
* {@code port}
* Yes
* {@code int}
* NA
*
* eBus peer port. Must be an integer value > zero and
* < 65,536.
*
*
*
* {@code bindPort}
* No
* {@code int}
* {@link ENetConfigure#ANY_PORT}
*
* Bind connection local port to this value. If set to
* {@link ENetConfigure#ANY_PORT} then local port is
* bound to any available ephemeral port.
*
*
*
* {@code inputBufferSize}
* No
* {@code int}
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Connection input buffer size. Limits number of
* bytes received at one time.
*
*
*
* {@code outputBufferSize}
* No
* {@code int}
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Connection output buffer size. Limits number of
* bytes transmitted at one time.
*
*
*
* {@code byteOrder}
* No
* {@link ByteOrder}
* {@link EConfigure#DEFAULT_BYTE_ORDER}
*
* eBus messages are encode/decoded in this byte order.
*
*
*
* {@code messageQueueSize}
* No
* {@code int}
* {@link EConfigure#DEFAULT_QUEUE_SIZE}
*
* Outbound message queue size. A zero queue size means
* the queue size is unlimited. Used only for TCP
* connection types.
*
*
*
* {@code selector}
* No
* {@code String}
* Default eBus selector.
*
* Remote connection channel is monitored by this eBus
* selector thread.
*
*
*
* {@code reconnect}
* No
* {@code boolean}
* {@code false}
*
* If {@code true}, then reconnects after unexpected
* connection loss. Used only for TCP connection types.
*
*
*
* {@code reconnectTime}
* Yes if {@code reconnect} is {@code true}
*
* {@code int} followed by time unit. Example:
* {@code 15 seconds}
*
* Zero
*
* Wait this time limit after disconnecting and each
* reconnect attempt.
*
*
*
* {@code heartbeatDelay}
* No
*
* {@code int} followed by time unit. Example:
* {@code 30 seconds}
*
* {@link #DEFAULT_HEARTBEAT_DELAY}
*
* Wait this delay after latest inbound message before
* sending heartbeat message. If zero then heartbeating
* is turned off.
*
*
*
* {@code heartbeatReplyDelay}
* No
*
* {@code int} followed by time unit. Example:
* {@code 500 milliseconds}
*
* {@link #DEFAULT_HEARTBEAT_REPLY_DELAY}
*
* Wait this time limit for a reply to a heartbeat message.
* A zero setting means wait indefinitely. If
* {@code heartbeatDelay} is zero, then this value is
* ignored.
*
*
*
* {@code canPause}
* No
* {@code boolean}
* {@code false}
*
* If {@code true}, then this connection may be paused
* according to {@link PauseConfig} settings.
*
*
*
*
* Example remote connection configuration.
*
* name : conn1
host : "127.0.0.1"
port : 12346
bindPort : 0
byteOrder : BIG_ENDIAN
selector : selector1
inputBufferSize : 8192
outputBufferSize : 65536
messageQueueSize : 100
reconnect : true
reconnectTime : 500ms
canPause : false
*
* @see Service
* @see PauseConfig
*/
public static final class RemoteConnection
extends AbstractConfig
implements Comparable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Remote eBus Internet address.
*/
private final InetSocketAddress mAddress;
/**
* Bind the local address to this TCP port.
*/
private final int mBindPort;
/**
* Register this connection with the specified selector.
*/
private final String mSelector;
/**
* If {@code true} then re-established lost connections.
*/
private final boolean mReconnectFlag;
/**
* Wait this many seconds between reconnect attempts.
*/
private final Duration mReconnectTime;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a remote connection configuration based on the
* builder's current parameters.
* @param builder contains the remote configuration
* parameters.
*/
/* package */ RemoteConnection(final ConnectionBuilder builder)
{
super (builder);
this.mAddress = builder.mAddress;
this.mBindPort = builder.mBindPort;
this.mSelector = builder.mSelector;
this.mReconnectFlag = builder.mReconnectFlag;
this.mReconnectTime = builder.mReconnectTime;
} // end of RemoteConnection(ConnectionBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Comparable Interface Implementation.
//
/**
* Compares {@code this} remote connection configuration
* with {@code conn}.
* @param conn compare with this object.
* @return < zero if {@code this} instance is less
* than {@code conn}; zero if {@code this} instance is
* equal to {@code conn}; > zero if {@code this} is
* greater than {@code conn}.
*/
@Override
public int compareTo(final RemoteConnection conn)
{
final InetSocketAddressComparator comparator =
new InetSocketAddressComparator();
return (comparator.compare(mAddress, conn.mAddress));
} // end of compareTo(RemoteConnection)
//
// end of Comparable Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if {@code o} is a
* non-{@code null} {@code RemoteConnection} instance
* with the same name; {@code false} otherwise.
* @param o Test equality with this object.
* @return {@code true} if {@code o} is a
* non-{@code null} {@code RemoteConnection} instance
* with the same address and connection type;
* {@code false} otherwise.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof RemoteConnection)
{
retcode =
mAddress.equals(
((RemoteConnection) o).mAddress);
}
return (retcode);
} // end of equals(Object)
/**
* Returns the remote connection address hash code.
* @return the remote connection address hash code.
*/
@Override
public int hashCode()
{
return (mAddress.hashCode());
} // end of hashCode()
/**
* Returns a text representation for this remote
* connection.
* @return a text representation for this remote
* connection.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append('[')
.append(mName)
.append("] address: ")
.append(mAddress)
.append("\n connection type: ")
.append(mConnectionType)
.append("\n bind port: ")
.append(mBindPort)
.append("\n input buffer size: ")
.append(mInputBufferSize)
.append("\n output buffer size: ")
.append(mOutputBufferSize)
.append("\n buffer byte order: ")
.append(mByteOrder)
.append("\n queue size: ")
.append(mMsgQueueSize)
.append("\n selector: ")
.append(mSelector)
.append("\n reconnect: ")
.append(mReconnectFlag);
if (mReconnectFlag)
{
retval.append("\n reconnect time: ")
.append(mReconnectTime);
}
if (mConnectionType == ConnectionType.SECURE_TCP)
{
retval.append("\n SSL context: ")
.append(mSSLContext);
}
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get methods.
//
/**
* Returns the remote eBus socket address.
* @return the remote eBus socket address.
*/
public InetSocketAddress address()
{
return (mAddress);
} // end of address()
/**
* Returns the connection bind port. This is the TCP
* port to which the local end is bound.
* @return the connection bind port.
*/
public int bindPort()
{
return (mBindPort);
} // end of bindPort()
/**
* Returns the selector information to which this
* connection is registered.
* @return selector information.
*/
public String selector()
{
return (mSelector);
} // end of connSelector()
/**
* Returns {@code true} if connection is to be
* re-established when lost.
* @return {@code true} if connection is to be
* re-established when lost.
*/
public boolean reconnectFlag()
{
return (mReconnectFlag);
} // end of reconnectFlag()
/**
* Returns the reconnect delay.
* @return the reconnect delay.
*/
public Duration reconnectTime()
{
return (mReconnectTime);
} // end of reconnectTime()
//
// end of Get methods.
//-------------------------------------------------------
} // end of class Remoteconnection
/**
* eBus uses a {@code Dispatcher} to forward messages to
* client. While Dispatchers cannot be accessed by an
* application, an application can configure Dispatchers. In
* order to configure a Dispatcher, you need to know how
* Dispatchers work.
*
* eBus wraps client callbacks in {@code Runnable} task
* instances. Each eBus client has an {@code Queue}
* containing the callback tasks for the client. So when a
* callback task is created, it is added to the client’s task
* queue. When the client task queue is empty and no callback
* task is being run, then the client is idle. When a new
* callback task is added to an idle client, then the client
* becomes runnable.
*
*
* When a client transitions from idle to runnable, then the
* client is added to its Dispatcher run queue. Each
* client is associated with one Dispatcher run queue. A
* Dispatcher has a run queue of runnable clients and one or
* more threads watch the run queue for runnable clients to
* arrive. One thread removes the client from the run queue
* and then starts executing the client’s queue tasks. The
* client is now in the running state.
*
*
* The queued client tasks are executed until either the task
* queue is empty or the client exhausts its run quantum. If
* a run quantum is used, then it is reset to the configured
* amount when the client transitions from idle to runnable
* or when the quantum is exhausted. When exhausted, the
* client transitions from running to runnable and put back
* on the LIFO run queue.
*
*
* Note: eBus does not use a
* preempting run quantum. If a client callback goes into an
* infinite loop, then that callback will take over the
* Dispatcher thread. The Dispatcher thread only checks if
* the quantum is exceeded when the callback returns. Since
* the callback in this example never returns, the Dispatcher
* thread does not detect that the run quantum is exceeded.
*
*
* Dispatchers are created either in an eBus configuration
* file which is loaded at JVM start using the Java command
* line option
* -Dnet.sf.eBus.config.jsonFile=<config file path>
* or dynamically using
* {@code net.sf.eBus.client.EFeed.createDispatcher(EConfigure.Dispatcher)}.
* The command line option is preferred since a Dispatcher
* must be instantiated prior to assigning an eBus
* client to the dispatcher. If using dynamic Dispatcher
* creation, care must be given to do so before eBus client
* assignment.
*
* Creating eBus Dispatchers
*
* An application may create dispatch threads with a given
* unique name and JSON properties listed below.
*
*
* Dispatcher JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code runQueueType}
* No
* {@link ThreadType}
* {@link #DEFAULT_THREAD_TYPE}
*
* Dispatcher thread is implemented as this type.
*
*
*
* {@code priority}
* No
*
* {@code int} ≥ {@link Thread#MIN_PRIORITY} and
* ≤ {@link Thread#MAX_PRIORITY}.
*
* {@link #DEFAULT_PRIORITY}
*
* Thread scheduling priority. Must be
*
*
*
* {@code quantum}
* No
* {@code int}
* {@link #DEFAULT_QUANTUM}
*
* Task run-time quantum. Does not pre-empt
* tasks which exceed this time limit.
*
*
*
* {@code numberThreads}
* No
* {@code int} > zero
* {@link #DEFAULT_NUMBER_THREADS}
*
* Dispatcher contains this many threads.
*
*
*
* {@code isDefault}
* No
* {@code boolean}
* {@code false}
*
* If {@code true} marks this as the default
* dispatcher. Only one dispatcher may be marked as the
* default.
*
*
*
* {@code classes}
* Yes if {@code isDefault} is {@code false}
*
* Array of {@link Class} names. May not be
* empty. Class names must be accessible to this JVM
* instance.
*
* NA
*
* Dispatcher handles instances of these classes.
*
*
*
* {@code threadAffinities}
* No
* Array of {@link ThreadAffinityConfigure}
* Empty list
*
* Optional OpenHFT thread affinity configurations.
*
*
*
*
* Example dispatcher configuration.
*
* name : d1
runQueueType : "spin+park"
spinLimit : 2500000
parkTime : 500ns
isDefault : true
priority : 5
quantum : 10000ns
numberThreads : 4
* Special Dispatchers
* There are two special, pre-defined eBus
* {@code Dispatcher} names:
* {@link DispatcherType#SWING "swing"} and
* {@link DispatcherType#JAVAFX "javafx"} (case insensitive).
* These {@code Dispatcher}s use the Swing/JavaFX GUI thread
* to deliver eBus messages to a client. This means that the
* client callback is free to update the GUI because the
* callback code is running on the GUI thread.
*
* Only two properties are supported by special
* {@code Dispatchers}: {@link #DEFAULT_KEY} and
* {@link #CLASSES_KEY}. All other properties are ignored by
* special {@code Dispatchers}. The reason is that the
* underlying GUI threads are implemented and configured by
* the GUI package and cannot be altered by eBus.
*
*
* It may be useful to define
* the GUI thread Dispatcher as the default Dispatcher and
* then create a separate eBus {@code Dispatcher} for
* non-GUI classes. This way, an application class updating
* the display will be assigned to the GUI thread without
* needing to add that class to the GUI Dispatcher classes
* property.
*
*/
public static final class Dispatcher
implements Comparable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* The unique dispatcher name.
*/
private final String mName;
/**
* The dispatcher type. Defines the dispatch method
* handle.
*/
private final DispatcherType mType;
/**
* Specifies how the dispatcher acquires the next client
* from the run queue: blocking, spinning, spin+park, or
* spin+yield.
*/
private final ThreadType mRunQueueType;
/**
* If {@link #mRunQueueType} is
* {@link ThreadType#SPINPARK spin+park} or
* {@link ThreadType#SPINYIELD spin+yield}, then spin
* this many times on trying to acquire the next client
* before parking/yielding.
*/
private final long mSpinLimit;
/**
* If {@link #mRunQueueType} is
* {@link ThreadType#SPINPARK spin+park}, then park for
* this many time limit before returning to spinning.
*/
private final Duration mParkTime;
/**
* The dispatcher threads run at this priority. Must be
* ≥ {@link Thread#MIN_PRIORITY} and
* ≤ {@link Thread#MAX_PRIORITY}.
*/
private final int mPriority;
/**
* Run quantum assigned to each client in this run queue.
*/
private final Duration mQuantum;
/**
* The dispatcher has this many threads.
*/
private final int mNumThreads;
/**
* {@code true} if this dispatcher is the default for all
* {@code EClient} instances not assigned to another,
* specific dispatcher.
*/
private final boolean mIsDefault;
/**
* Contains the classes assigned to this dispatcher. Will
* be an empty array if {@link #mIsDefault} is
* {@code true}.
*/
private final Class>[] mClasses;
/**
* Optional thread affinity configuration. Defaults to
* {@code null}. Thread affinity should be considered
* when using {@link ThreadType#SPINNING} thread type.
*/
private final ThreadAffinityConfigure[] mAffinity;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a Dispatcher group configuration based on
* {@code builder}'s current setting.
* @param builder contains the Dispatcher configuration
* parameters.
*/
/* package */ Dispatcher(final DispatcherBuilder builder)
{
this.mName = builder.mName;
this.mType = builder.mType;
this.mRunQueueType = builder.mRunQueueType;
this.mSpinLimit = builder.mSpinLimit;
this.mParkTime = builder.mParkTime;
this.mPriority = builder.mPriority;
this.mQuantum = builder.mQuantum;
this.mNumThreads = builder.mNumThreads;
this.mIsDefault = builder.mIsDefault;
this.mClasses = builder.mClasses;
this.mAffinity = builder.mAffinity;
} // end of Dispatcher(DispatcherBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Comparable Interface Implementation.
//
@Override
public int compareTo(final Dispatcher o)
{
return (mName.compareTo(o.mName));
} // end of compareTo(Dispatcher)
//
// end of Comparable Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof Dispatcher)
{
retcode = (mName.equals(((Dispatcher) o).mName));
}
return (retcode);
} // end of equals(Object)
@Override
public int hashCode()
{
return (mName.hashCode());
} // end of hashCode()
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append('[')
.append(mName)
.append("]\n priority: ")
.append(mPriority)
.append("\n is default: ")
.append(mIsDefault);
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get methods.
//
/**
* Returns the unique dispatcher name.
* @return dispatcher thread name.
*/
public String name()
{
return (mName);
} // end of name()
/**
* Returns the dispatch method type.
* @return dispatch method type.
*/
public DispatcherType dispatchType()
{
return (mType);
} // end of dispatchType()
/**
* Returns the thread type which defines how the next
* client is acquired from the run queue.
* @return run queue-acquisition type.
*/
public ThreadType runQueueType()
{
return (mRunQueueType);
} // end of runQueueType()
/**
* Returns the spin limit used by
* {@link ThreadType#SPINPARK spin+park} and
* {@link ThreadType#SPINYIELD spin+yield} thread types.
* @return spin limit.
*/
public long spinLimit()
{
return (mSpinLimit);
} // end of spinLimit()
/**
* Returns the park time used by
* {@link ThreadType#SPINPARK spin+park} thread type.
* @return nanosecond park time.
*/
public Duration parkTime()
{
return (mParkTime);
} // end of parkTime()
/**
* Returns {@code true} if this user-defined dispatcher
* is the default dispatcher.
* @return {@code true} if the default dispatcher.
*/
public boolean isDefault()
{
return (mIsDefault);
} // end of isDefault()
/**
* Returns the classes assigned to this dispatcher.
* Returned array may be empty but will not be
* {@code null}.
* @return dispatcher class array.
*/
public Class>[] classes()
{
return (Arrays.copyOf(mClasses, mClasses.length));
} // end of classes()
/**
* Returns the dispatcher thread priority.
* @return thread priority.
*/
public int priority()
{
return (mPriority);
} // end of priority()
/**
* Returns the client run-time quantum assigned by this
* dispatcher.
* @return client run-time quantum.
*/
public Duration quantum()
{
return (mQuantum);
} // end of quantum()
/**
* Returns the number of threads in this dispatcher.
* @return dispatcher thread count.
*/
public int numberThreads()
{
return (mNumThreads);
} // end of numberThreads()
/**
* Returns thread affinity and {@code null} if no
* affinity is set. This affinity is used to associate a
* thread with a particular CPU.
* @return thread affinity.
*/
public ThreadAffinityConfigure[] affinity()
{
return (mAffinity);
} // end of affinity()
//
// end of Get methods.
//-------------------------------------------------------
} // end of class Dispatcher
/**
* If a remote connection is to be paused, then this is the
* allowed pause configuration. When defined for a connection
* acceptor, only the pause duration and maximum backlog size
* properties are used. A connection initiator has all
* properties used.
*
* When a connection is paused, the actual connection is
* dropped but all the subscriptions and advertisements are
* kept in place. That means that system and application
* messages destined for the other side are queued, ready
* for transmit when the connection is resumed. While all
* system messages are queued, the number of queued
* application messages may be limited. When that limit
* is reached, further application messages are discard as
* per the configured {@link EConfigure.DiscardPolicy}.
*
*
* Note that for accepted connections the discard policy used
* when the maximum message backlog size is reached is set by
* the connection initiator when requesting a connection
* pause.
*
*
* This feature is targeted for eBus-based applications
* running on mobile devices. Such devices cannot maintain
* active connections for long without overheating and
* draining the battery.
*
*
* The pause configuration JSON properties for an eBus
* service are:
*
*
* Service Pause JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code pauseTime}
* Yes
* {@link Duration}
* NA
*
* Maximum allowed pause duration. Actual pause
* duration in the minimum of client and server values.
*
*
*
* {@code maxBacklogSize}
* Yes
* {@code int} ≥ zero
* NA
*
* Maximum allowed pending message backlog. If zero,
* then backlog size is unlimited. If maximum size is
* reached on the server side, then messages are
* discarded according to the client-specified
* {@link DiscardPolicy discard policy}.
*
*
*
*
* The pause configuration JSON properties for an eBus
* remote connection are:
*
*
* Remote Connection Pause JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code pauseTime}
* Yes
* {@link Duration}
* NA
*
* Maximum allowed pause duration. Actual pause
* duration in the minimum of client and server values.
* When limit is reached, connection is resumed.
*
*
*
* {@code maxBacklogSize}
* Yes
* {@code int} ≥ zero
* NA
*
* Maximum allowed pending message backlog. If zero,
* then backlog size is unlimited. If maximum size is
* reached on client side, then messages are discarded
* as per the {@link DiscardPolicy discard policy}.
*
*
*
* {@code discardPolicy}
* Yes
* {@link DiscardPolicy}
* No
*
* Message discard policy to be used on both sides,
* client and server.
*
*
*
* {@code idleTime}
* Yes
* {@link Duration}
* NA
*
* When no messages are sent in the time limit,
* connection is paused.
*
*
*
* {@code maxConnectTime}
* Yes
* {@link Duration}
* NA
*
* Maximum time a connection may remain up before
* being paused, regardless of message transmit rate.
*
*
*
* {@code resumeOnBacklogSize}
* No
* {@code int} ≥ zero
* Zero
*
* If message backlog reaches this size, then resume
* connection. A zero value means that this feature is
* turned off. If {@code maxBacklogSize} is > zero,
* then this value should be <
* {@code maxBacklogSize} to be effective.
*
*
*
*
* Example pause config - service.
*
* pauseTime : 10m
maxBacklogSize : 50
*
* Example pause config - remote connection.
*
* pauseTime : 5m
maxBacklogSize : 100
discardPolicy : YOUNGEST_FIRST
idleTime : 1m
maxConnectTime : 2m
resumeOnBacklogSize : 10
*/
public static final class PauseConfig
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* If {@link #mCanPause} is {@code true}, then this is
* the maximum allowed pause duration.
*/
private final Duration mDuration;
/**
* If {@link #mCanPause} is {@code true}, then this is
* the maximum allowed message backlog size.
*/
private final int mMaxBacklogSize;
/**
* If {@link #mCanPause} is {@code true}, then this is
* the message discard policy used when
* {@link #mMaxBacklogSize} is breached. This policy is
* set by the connection initiator and used by the
* connection acceptor.
*/
private final DiscardPolicy mDiscardPolicy;
/**
* If a remote connection does not send or receive any
* messages for this duration, the connection is paused.
*/
private final Duration mIdleTime;
/**
* A remote connection remains connected for this
* duration at most. Once this time limit is reached, the
* connection is automatically paused - independent of
* when the last message was sent or received.
*/
private final Duration mMaxConnectTime;
/**
* When the pending transmit queue reaches this limit
* (application messages only), then the client
* connection is resumed. This feature is turned off
* when set to zero.
*/
private final int mResumeOnQueueLimit;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private PauseConfig(final PauseBuilder builder)
{
mDuration = builder.mDuration;
mMaxBacklogSize = builder.mMaxBacklogSize;
mDiscardPolicy = builder.mDiscardPolicy;
mIdleTime = builder.mIdleTime;
mMaxConnectTime = builder.mMaxConnectTime;
mResumeOnQueueLimit = builder.mResumeOnBacklogSize;
} // end of PauseConfig(PauseBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns the pause configuration as text.
* @return textual representation of the pause
* configuration.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append("[duration=").append(mDuration)
.append(", backlog size=").append(mMaxBacklogSize);
if (mDiscardPolicy != null)
{
retval.append(", discard policy=")
.append(mDiscardPolicy);
}
retval.append(", idle time=").append(mIdleTime)
.append(", max connect time=").append(mMaxConnectTime)
.append(", resume-on-send=").append(mResumeOnQueueLimit)
.append("]");
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns the maximum allowed pause duration.
* @return pause duration.
*/
public Duration duration()
{
return (mDuration);
} // end of duration()
/**
* Returns the maximum allowed message backlog size.
* @return message backlog size.
*/
public int maxBacklogSize()
{
return (mMaxBacklogSize);
} // end of maxBacklogSize()
/**
* Returns the message discard policy used when the
* maximum message backlog size is breached. This policy
* is set by the connection initiator.
* @return message discard policy.
*/
public DiscardPolicy discardPolicy()
{
return (mDiscardPolicy);
} // end of discardPolicy()
/**
* Returns the amount of time wherein no messages are
* sent or received on the connection before the
* connection is paused.
* @return connection idle time.
*/
public Duration idleTime()
{
return (mIdleTime);
} // end of idleTime()
/**
* Returns the maximum time a connection may remain up
* before being paused. Note that this pausing is
* irrespective of the message arrival or departure.
* @return maximum connection time between pauses.
*/
public Duration maxConnectTime()
{
return (mMaxConnectTime);
} // end of maxConnectTime()
/**
* Returns the transmit queue limit which triggers an
* automatic client connection resumption. If zero is
* returned, then this trigger is disabled.
* @return resume on reaching transmit queue limit.
*/
public int resumeOnQueueLimit()
{
return (mResumeOnQueueLimit);
} // end of resumeOnBacklogSize()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class PauseConfig
/**
* A {@code MulticastConnection} instance is used to create
* a {@code net.sf.eBus.client.EMulticastConnection} and
* contains all the settings which define a multicast
* connection. The JSON properties are:
*
* Multicast JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code name}
* Yes
* {@code String}
* NA
*
* Unique multicast connection name. Used for logging
* purposes only but must be provided and must be
* unique within the application instance.
*
*
*
* {@code role}
* Yes
* {@link MulticastRole}
* NA
*
* Specifies whether this is a publisher or subscriber
* multicast connection.
*
*
*
* {@code group}
* Yes
* {@link InetAddress}
* NA
*
* Multicast group address.
*
*
*
* {@code targetPort}
* Yes
* {@code int} > zero and < 65,536.
* NA
*
* Send datagram packets to the multicast group on this
* port.
*
*
*
* {@code notifications}
* Yes
* Array of {@link McastNotifyConfig}
* NA
*
* Notification message keys used to create
* {@code net.sf.eBus.client.EMultiFeed} instances.
*
*
*
* {@code networkInterface}
* Yes
* {@link NetworkInterface}
* NA
*
* Local interface used to receive multicast datagram
* packets.
*
*
*
* {@code sources}
* No
* Array of {@link InetAddress}
* Empty list.
*
* Multicast packets are accepted only from those
* addresses contained in the {@code sources} list.
* If an empty list then packets are accepted from any
* source.
*
*
*
* {@code bindPort}
* No
* {@code int} ≥ zero and < 65,536.
* {@link ENetConfigure#ANY_PORT}
*
* Bind the socket's local side to the given port.
*
*
*
* {@code protocolFamily}
* Yes
* {@link StandardProtocolFamily}
* NA
*
* Specifies whether the datagram channel is opened
* using IPv4 or IPv6.
*
*
*
* {@code byteOrder}
* No
* {@link ByteOrder}
* {@link #DEFAULT_BYTE_ORDER}
*
* Notifications posted to the multicast group are
* encoded in this byte ordering.
*
*
*
* {@code selector}
* No
* {@code String}
* {@link ENetConfigure#defaultSelector()}
*
* Multicast socket is monitored by this named selector
* thread.
*
*
*
* {@code inputBufferSize}
* No
* {@code int} > zero
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Size of inbound socket {@code ByteBuffer} in bytes.
* Defines maximum allowed size of encoded messages.
*
*
*
* {@code outputBufferSize}
* No
* {@code int} > zero
* {@link ENetConfigure#DEFAULT_BUFFER_SIZE}
*
* Size of outbound socket {@code ByteBuffer} in bytes.
*
*
*
*
* Example multicast connection.
*
*
name : mc1
role : PUBLISHER
group : "225.4.5.6"
targetPort : 12358
networkInterface : en5
sources : []
bindPort : 5000
protocolFamily : INET
byteOrder : LITTLE_ENDIAN
selector : s1
inputBufferSize : 512
outputBufferSize : 512
notifications : [ ... ]
*
* A multicast connection is configured to either publish
* notification messages to the group or subscribe
* to notification messages. A multicast connection cannot
* both publish and subscribe to the same group. That said,
* an application may have separate multicast connections:
* one for publishing and one for subscribing. It is not
* recommended that an application have more than one
* multicast group connection with the same role and message
* keys as this results in needless encoding/decoding effort.
* It will also result in the same notification being
* published multiple times.
*
*
* Note that multicast connections do not support
* request/reply messages.
*
*
* @see MulticastBuilder
*/
public static final class MulticastConnection
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* Multicast connection unique name. Used for logging
* purposes only.
*/
private final String mName;
/**
* This multicast connection is either a multicast
* publisher or subscriber.
*/
private final MulticastRole mRole;
/**
* Multicast group address.
*/
private final InetAddress mGroup;
/**
* Post multicast messages to this UDP port.
*/
private final int mTargetPort;
/**
* Open multicast connection on this network interface.
*/
private final NetworkInterface mNetworkIF;
/**
* If not {@code null} then accept datagrams posted from
* these addresses only.
*/
private final List mSources;
/**
* Bind socket to this local address. If {@code null}
* then socket is bound to an automatically assigned
* address.
*/
private final InetAddress mBindAddress;
/**
* Bind local address to this UDP port.
*/
private final int mBindPort;
/**
* Open multicast socket using this network protocol.
*/
private final ProtocolFamily mFamily;
/**
* The input and output buffer use this byte ordering.
*/
private final ByteOrder mByteOrder;
/**
* Register this connection with the specified selector.
*/
private final String mSelector;
/**
* Input buffer size.
*/
private final int mInputBufferSize;
/**
* Output buffer size.
*/
private final int mOutputBufferSize;
/**
* Notification multi-feed keys.
*/
private final List mNotifications;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new multicast connection configuration based
* on the builder settings.
* @param builder contains multicast connection settings.
*/
private MulticastConnection(final MulticastBuilder builder)
{
mName = builder.mName;
mRole = builder.mRole;
mGroup = builder.mGroup;
mTargetPort = builder.mTargetPort;
mNetworkIF = builder.mNetworkIF;
mSources = builder.mSources;
mBindAddress = builder.mBindAddress;
mBindPort = builder.mBindPort;
mFamily = builder.mFamily;
mByteOrder = builder.mByteOrder;
mSelector = builder.mSelector;
mInputBufferSize = builder.mInputBufferSize;
mOutputBufferSize = builder.mOutputBufferSize;
mNotifications = builder.mNotifications;
} // end of MulticastConnection(MulticastBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns text containing the multicast connection
* configuration.
* @return multicast connection configuration as text.
*/
@Override
public String toString()
{
String sep = "";
final StringBuilder retval = new StringBuilder();
retval.append("[name=").append(mName)
.append(", role=").append(mRole)
.append(", group=")
.append(mGroup).append(':').append(mTargetPort)
.append(", net I/F=").append(mNetworkIF)
.append(", bind address=").append(mBindAddress)
.append(", bind port=").append(mBindPort)
.append(", byte order=").append(mByteOrder)
.append(", selector=").append(mSelector)
.append(", in buffer size=")
.append(mInputBufferSize)
.append(", out buffer size=")
.append(mOutputBufferSize)
.append(", notifications={");
for (McastNotifyConfig config : mNotifications)
{
retval.append(sep).append(config);
sep = ", ";
}
return (retval.append("}]").toString());
} // end of toString()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns unique multicast connection name. This name is
* used for logging purposes only.
* @return multicast connection name.
*/
public String name()
{
return (mName);
} // end of name()
/**
* Returns multicast connection role which is either
* publisher or subscriber.
* @return multicast connection role.
*/
public MulticastRole role()
{
return (mRole);
} // end of role()
/**
* Returns multicast group address.
* @return multicast group address.
*/
public InetAddress group()
{
return (mGroup);
} // end of group()
/**
* Returns multicast target port. This port is used for
* {@link java.nio.channels.DatagramChannel#send(java.nio.ByteBuffer, java.net.SocketAddress) posting}
* messages to the multicast group.
* @return multicast target port.
*/
public int targetPort()
{
return (mTargetPort);
} // end of targetPort()
/**
* Socket address containing the {link #group()} and
* {@link #targetPort()}.
* @return Socket address containing the multicast group
* address and port.
*/
public InetSocketAddress groupAddress()
{
return (new InetSocketAddress(mGroup, mTargetPort));
} // end of groupAddress()
/**
* Returns network interface associated with the
* multicast group address.
* @return multicast group network interface.
*/
public NetworkInterface networkInterface()
{
return (mNetworkIF);
} // end of networkInterface()
/**
* Returns the optional source address list. May return
* {@code null}. If not {@code null} or empty, datagram
* packets are accepted only from the listed addresses;
* otherwise packets are accepted from any source.
* @return source address list. May be {@code null}.
*/
public List sources()
{
return (mSources);
} // end of sources()
/**
* Returns the address to which the local socket side is
* bound. May return {@code null}.
* @return local socket side bind address.
*/
public InetAddress bindAddress()
{
return (mBindAddress);
} // end of bindAddress()
/**
* Returns port to which local socket side is bound. If
* {@link ENetConfigure#ANY_PORT} is returned then local
* side is bound to an ephemeral port.
* @return local socket side bind port.
*/
public int bindPort()
{
return (mBindPort);
} // end of bindPort()
/**
* Returns multicast group protocol family.
* @return multicast group protocol family.
*/
public ProtocolFamily protocolFamily()
{
return (mFamily);
} // end of protocolFamily()
/**
* Returns byte order in which messages are encoded and
* decoded.
* @return message encoding, decoding byte order.
*/
public ByteOrder byteOrder()
{
return (mByteOrder);
} // end of byteOrder()
/**
* Returns selector thread used to monitor this multicast
* connection.
* @return selector thread name.
*/
public String selector()
{
return (mSelector);
} // end of selector()
/**
* Inbound, encoded messages are copied into a
* {@link java.nio.ByteBuffer} of this size (in bytes).
* @return buffer size in bytes.
*/
public int inputBufferSize()
{
return (mInputBufferSize);
} // end of inputBufferSize()
/**
* Outbound message are encoded into a
* {@link java.nio.ByteBuffer} of this size (in bytes).
* @return buffer size in bytes.
*/
public int outputBufferSize()
{
return (mOutputBufferSize);
} // end of outputBufferSize()
/**
* Returns notification message keys which are either
* posted to or received from the multicast group
* depending on the role.
* @return notification message keys set.
*/
public List notifications()
{
return (mNotifications);
} // end of notifications()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class MulticastConnection
/**
* Contains the notification message class and subject(s)
* which defines the messages either published by a
* {@link MulticastConnection} or subscribed to. Subjects are
* defined either as a list containing one or more subjects
* or as a subject query pattern. A subject list is fixed and
* new subjects may not be added to subject list
* once the multicast connection is created. Subject query
* patterns may be either fixed or dynamic. If fixed, then
* the pattern is matched against existing subjects when the
* multicast connection is created and the matching subjects
* remain unchanged afterwards. If dynamic, then new subjects
* are compared with the subject query and, if the new
* subject matches the query, is added to the multicast feed.
*
* JSON propertie used to configure a multicast notification
* are:
*
*
* Multicast Feed JSON Properties
*
* Property
* Required?
* Type
* Default
* Description
*
*
* {@code multifeedType}
* Yes
* {@link MultifeedType}
* NA
*
* A multifeed notification feed type is either list or
* query.
*
*
*
* {@code messageClass}
* Yes
* {@code Class}
* NA
*
* {@code ENotificationMessage} subclass name.
*
*
*
* {@code subjectList}
*
* Yes if {@code multifeedType} is
* {@link MultifeedType#LIST}
*
* Array of {@code String}
* NA
*
* Non-empty list of notification subjects.
*
*
*
* {@code subjectQuery}
*
* Yes if {@code multifeedType} is
* {@link MultifeedType#QUERY}
*
* {@code String}
* NA
*
* {@link Pattern Regular expression query} used to
* select notification subjects.
*
*
*
* {@code isDynamic}
*
* Yes if {@code multifeedType} is
* {@link MultifeedType#QUERY}
*
* {@code boolean}
* NA
*
* {@code true} if query is applied to newly added
* subjects and {@code false} if query is used only
* once on multicast connection start.
*
*
*
*
* Example multicast configuration configuration - query.
*
* multifeedType : QUERY
messageClass : "net.sf.eBus.client.EquityTradeMessage"
subjectQuery : "[A-M].+"
isDynamic : true
*
* Example multicast configuration configuration - list.
*
* multifeedType : LIST
messageClass : "net.sf.eBus.client.TopOfBookMessage"
subjectList : [
"ABC",
"DEF",
"GHI",
"XYZ"
]
*/
public static final class McastNotifyConfig
implements Comparable
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* This is either a list-based or a query-based
* notification multi-feed.
*/
private final MultifeedType mType;
/**
* eBus notification message class.
*/
private final String mMessageClass;
/**
* Multi-feed subject list. Set to {@code null} if
* multi-feed type is {@code QUERY}.
*/
private final List mSubjectList;
/**
* Multi-feed query used to select subjects. Set to
* {@code null} if multi-feed type is {@code LIST}.
*/
private final Pattern mSubjectQuery;
/**
* If {@code true} then set list for notification subject
* updates. Is set to {@code false} if multi-feed type is
* {@code LIST}.
*/
private final boolean mIsDynamic;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private McastNotifyConfig(final McastNotifyBuilder builder)
{
mType = builder.mType;
mMessageClass = builder.mMessageClass;
mSubjectList = builder.mSubjectList;
mSubjectQuery = builder.mSubjectQuery;
mIsDynamic = builder.mIsDynamic;
} // end of McastNotifyConfig(McastNotifyBuilder)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Comparable Interface Implementation.
//
@Override
public int compareTo(final McastNotifyConfig o)
{
return (mMessageClass.compareTo(o.mMessageClass));
} // end of compareTo(McastNotifyConfig)
//
// end of Comparable Interface Implementation.
//-------------------------------------------------------
//-------------------------------------------------------
// Object Method Overrides.
//
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append("[class=").append(mMessageClass)
.append(", type=").append(mType);
if (mType == MultifeedType.LIST)
{
String sep = "";
retval.append(", list={");
for (String subject : mSubjectList)
{
retval.append(sep).append(subject);
sep = ", ";
}
retval.append("}");
}
else
{
retval.append(", query=")
.append(mSubjectQuery.pattern());
}
return (retval.append("]").toString());
} // end of toString()
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof McastNotifyConfig)
{
final McastNotifyConfig config =
(McastNotifyConfig) o;
retcode =
mMessageClass.equals(config.mMessageClass);
}
return (retcode);
} // end of equals(Object)
@Override
public int hashCode()
{
return (mMessageClass.hashCode());
} // end of hashCode()
//
// end of Object Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Get Methods.
//
/**
* Returns multi-feed type.
* @return multi-feed type.
*/
public MultifeedType feedType()
{
return (mType);
} // end of feedType()
/**
* Returns message class.
* @return message class.
*/
public String messageClass()
{
return (mMessageClass);
} // end of messageClass()
/**
* Returns subject list. Will return {@code null} if
* {@link #feedType()} is {@code QUERY}.
* @return subject list.
*/
public List subjectList()
{
return (mSubjectList);
} // end of subjectList()
/**
* Returns notification subject query. Will return
* {@code null} if {@link #feedType()} is {@code LIST}.
* @return subject query.
*/
public Pattern subjectQuery()
{
return (mSubjectQuery);
} // end of subjectQuery()
/**
* Returns {@code true} if this is a dynamic query
* multi-feed.
* @return dynamic query multi-feed flag.
*/
public boolean isDynamic()
{
return (mIsDynamic);
} // end of isDynamic()
//
// end of Get Methods.
//-------------------------------------------------------
} // end of class McastNotifyConfig
//
// Configuration builder classes.
//
/**
* Base class for {@link ServerBuilder} and
* {@link ConnectionBuilder}, containing the properties
* common to both.
*
* @param builder subclass.
*
* @author Charles W. Rapp
*/
@SuppressWarnings ("unchecked")
public abstract static class AbstractBuilder
{
//-----------------------------------------------------------
// Member data.
//
/**
* Unique service or connection name.
*/
protected String mName;
/**
* Server-accepted or remote connection type is either
* plain text TCP or SSL/TLS secure TCP.
*/
private ConnectionType mConnectionType;
/**
* Maximum input buffer size. May be overridden by
* {@link javax.net.ssl.SSLSession#getApplicationBufferSize()}
* when using a secure TCP connection.
*/
protected int mInputBufferSize;
/**
* Maximum output buffer size. May be override by
* {@link javax.net.ssl.SSLSession#getPacketBufferSize()}
* when using a secure TCP connection.
*/
protected int mOutputBufferSize;
/**
* Code and decode messages in this byte order.
*/
protected ByteOrder mByteOrder;
/**
* Maximum eBus message outbound queue size for remote
* connections. When the queue outbound message count
* exceeds this size, the remote connection is
* automatically closed and the outbound messages
* discarded.
*/
protected int mMsgQueueSize;
/**
* Send a heartbeat message after this many milliseconds
* of inbound message inactivity.
*/
protected Duration mHbDelay;
/**
* The number of milliseconds the far-end has to respond
* to a heartbeat. If the response is not received after
* this many milliseconds, the remote connection is
* automatically closed.
*/
protected Duration mHbReplyDelay;
/**
* The SSL/TLS context used for a secure TCP connection.
*/
protected SSLContext mSSLContext;
/**
* Set to {@code true} if remote connections may be
* paused.
*/
protected boolean mCanPause;
/**
* Contains the pause parameters. Will be {@code null} if
* {@link #mCanPause} is {@code false}.
*/
protected PauseConfig mPauseConfig;
/**
* Set to {@code true} if this builder is used when
* loading a configuration. This flag is used to detect
* when a secure TCP session is defined in a
* configuration as opposed to built by an application.
* If so, then the session definition is not required
* to define the SSL context.
*/
protected boolean mLoaderFlag;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new abstract configuration builder.
*/
protected AbstractBuilder()
{
mName = null;
mConnectionType = ConnectionType.TCP;
mInputBufferSize = 0;
mOutputBufferSize = 0;
mByteOrder = DEFAULT_BYTE_ORDER;
mMsgQueueSize = 0;
mHbDelay = DEFAULT_HEARTBEAT_DELAY;
mHbReplyDelay = DEFAULT_HEARTBEAT_REPLY_DELAY;
mSSLContext = null;
mCanPause = false;
mPauseConfig = null;
mLoaderFlag = false;
} // end of AbstractBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets the service/connection name.
* @param name service/connection name.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code name} is {@code null} or empty.
*/
public final T name(final String name)
{
if (Strings.isNullOrEmpty(name))
{
throw (
new ConfigException.BadValue(
NAME_KEY, INVALID_NAME));
}
mName = name;
return ((T) this);
} // end of name(String)
/**
* Sets the underlying channel type.
* @param connType channel type.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code connType} is {@code null}.
*/
public final T connectionType(final ConnectionType connType)
{
if (connType == null)
{
throw (
new ConfigException.BadValue(
CONN_TYPE_KEY, "connType is null"));
}
mConnectionType = connType;
return ((T) this);
} // end of connectionType(ConnectionType)
/**
* Sets the input buffer size for the connection. If
* {@code size} is zero, then the default input buffer
* size is used.
* @param size connection input buffer size.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code size} < zero.
*/
public final T inputBufferSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
INBUFFER_SIZE_KEY,
"input buffer size < 0"));
}
mInputBufferSize = size;
return ((T) this);
} // end of inputBufferSize(int)
/**
* Sets the output buffer size for the connection. If
* {@code size} is zero, then the default output buffer
* size is used.
* @param size connection output buffer size.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code size} < zero.
*/
public final T outputBufferSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
OUTBUFFER_SIZE_KEY,
"output buffer size < 0"));
}
mOutputBufferSize = size;
return ((T) this);
} // end of outputBufferSize(int)
/**
* Sets the byte order used by the connection.
* @param byteOrder connection serialize and
* de-serialize messages using this byte order.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code byteOrder} is {@code nuill}.
*/
public final T byteOrder(final ByteOrder byteOrder)
{
if (byteOrder == null)
{
throw (
new ConfigException.BadValue(
BYTE_ORDER_KEY, "byteOrder is null"));
}
mByteOrder = byteOrder;
return ((T) this);
} // end of byteOrder(ByteOrder)
/**
* Sets the maximum queue size for the connection. When
* the message queue size reaches this maximum, the
* connection is automatically closed. If zero, then the
* default maximum queue size is used.
* @param size message queue maximum size.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code size} < zero.
*/
public final T messageQueueSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
MSG_QUEUE_SIZE_KEY,
"message queue size < 0"));
}
mMsgQueueSize = size;
return ((T) this);
} // end of messageQueueSize(int)
/**
* Sets the heartbeat delay used for the connection. If
* no message is received after {@code delay}
* milliseconds, then a heartbeat is sent. If
* {@code delay} is zero, then heartbeating is turned
* off.
* @param delay millisecond heartbeat rate.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code delay} < zero.
*/
public final T heartbeatDelay(final Duration delay)
{
if (delay == null)
{
throw (
new ConfigException.BadValue(
HB_DELAY_KEY, "heartbeat delay is null"));
}
if (delay.isNegative())
{
throw (
new ConfigException.BadValue(
HB_DELAY_KEY, "heartbeat delay < 0"));
}
mHbDelay = delay;
return ((T) this);
} // end of heartbeatDelay(Duration)
/**
* Sets the heartbeat reply delay used for the
* connection. The remote eBus application has this
* many milliseconds to reply to a heartbeat. If no reply
* is received after {@code delay} milliseconds, the
* connection is closed. This value is ignored if the
* heartbeat delay is zero.
* @param delay millisecond heartbeat reply delay.
* @return {@code this} service/connection builder.
* @throws ConfigException
* if {@code delay} is {@code null} or < zero.
*/
public final T heartbeatReplyDelay(final Duration delay)
{
if (delay == null)
{
throw (
new ConfigException.BadValue(
HB_REPLY_DELAY_KEY,
"heartbeat reply delay is null"));
}
if (delay.isNegative())
{
throw (
new ConfigException.BadValue(
HB_REPLY_DELAY_KEY,
"heartbeat reply delay < 0"));
}
mHbReplyDelay = delay;
return ((T) this);
} // end of heartbeatReplyDelay(Duration)
/**
* Sets the SSL/TLS context used by a secure location.
* This should be called only if the connection type
* is set to {@link ConnectionType#SECURE_TCP}.
* @param context secure TCP context.
* @return {@code this} server/connection builder.
* @throws ConfigException
* if {@code context} is {@code null}.
*/
public final T sslContext(final SSLContext context)
{
if (context == null)
{
throw (
new ConfigException.BadValue(
SSL_CONTEXT_KEY, "context is null"));
}
mSSLContext = context;
return ((T) this);
} // end of sslContext(SSLContext)
/**
* Sets a flag specifying whether accepted connections
* may be paused ({@code true}) or not.
* @param flag pause-able connection flag.
* @return {@code this} connection builder.
*/
public final T canPause(final boolean flag)
{
mCanPause = flag;
return ((T) this);
} // end of canPause(boolean)
/**
* Sets the pause configuration for the connection.
*
* {@link #canPause(boolean) canPause(boolean)}
* must be set to {@code true} before setting
* the pause configuration.
*
* @param pc pause configuration
* @return {@code this} server/connection builder.
* @throws ConfigException
* if {@link #canPause(boolean)} returns {@code false}
* (meaning the connection cannot be pause) or if
* {@code pc} is {@code null}.
*/
public final T pauseConfig(final PauseConfig pc)
{
if (!mCanPause)
{
throw (
new ConfigException.Generic(
"connection cannot be paused"));
}
if (pc == null)
{
throw (
new ConfigException.BadValue(
PAUSE_KEY, "pause config is null"));
}
mPauseConfig = pc;
return ((T) this);
} // end of pauseDuration(Duration)
/**
* Copies the values found in {@code config} to this
* configuration. This method is used to modify an
* existing immutable configuration.
* @param config copy this configuration's values.
* @return {@code this} server/connection builder.
*/
protected T configuration(final AbstractConfig config)
{
mName = config.name();
mConnectionType = config.connectionType();
mInputBufferSize = config.inputBufferSize();
mOutputBufferSize = config.outputBufferSize();
mByteOrder = config.byteOrder();
mMsgQueueSize = config.messageQueueSize();
mHbDelay = config.heartbeatDelay();
mHbReplyDelay = config.heartbeatReplyDelay();
mSSLContext = config.sslContext();
mCanPause = config.canPause();
mPauseConfig = config.pauseConfiguration();
return ((T) this);
} // end of configuration(AbstractConfig)
/**
* Sets the loader flag to the given value.
* @param flag loader flag value.
* @return {@code this} server/connection builder.
*/
protected final T loaderFlag(final boolean flag)
{
mLoaderFlag = flag;
return ((T) this);
} // end of loaderFlag(flag)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Validates builder arguments are correctly set,
* listing validation errors in {@code problems} list.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
protected Validator validate(final Validator problems)
{
return (
problems.requireNotNull(mName, NAME_KEY)
.requireTrue((!mConnectionType.isSecure() ||
mLoaderFlag ||
mSSLContext != null),
SSL_CONTEXT_KEY,
"SSL context not provided for secure connection")
.requireTrue((mConnectionType.mIsSecure ||
mSSLContext == null),
SSL_CONTEXT_KEY,
"SSL context provided for non-secure connection")
.requireTrue((!mCanPause ||
mPauseConfig != null),
PAUSE_KEY,
"pause configuration not set"));
} // end of validate()
} // end of class AbstractBuilder
/**
* Constructs an {@link EConfigure.Service} instance based on
* the parameters set via this builder's API. The minimally
* allowed configuration is the service name, connection type
* and port. The remaining parameters are set to the
* following defaults:
*
* -
* connection type: {@link ConnectionType#TCP} - plain
* text TCP connection.
*
* -
* address filter: {@code null} - no address filter
* applied.
*
* -
* input buffer size: zero - use system default buffer
* size.
*
* -
* output buffer size: zero - use system default buffer
* size.
*
* -
* byte order: little-endian.
*
* -
* maximum message queue size: zero - unlimited queue
* size.
*
* -
* service selector thread:
* {@code AsyncChannel.defaultSelector}.
*
* -
* accepted connection select thread:
* {@code AsyncChannel.defaultSelector}.
*
* -
* heartbeat millisecond delay: zero - heartbeating off.
*
* -
* heartbeat millisecond reply delay: zero - wait
* indefinitely for a reply.
*
*
*
* If either the service name or port are not set, then
* {@link #build()} will throw an exception.
*
* Example building an {@code EServer}
* final AddressFilter filter = ...;
final SSLContext secureContext = ...;
final EConfigure.ServerBuilder builder = EConfigure.serverBuilder();
EServer.openServer(builder.name("AppServer")
.port(6789)
.connectionType(EConfigure.ConnectionType.SECURE_TCP)
.sslContext(secureContext)
.addressFilter(filter)
.inputBufferSize(1_024)
.outputBufferSize(1_024)
.byteOrder(ByteOrder.BIG_ENDIAN)
.messageQueueSize(10_000)
.serviceSelector("svcSelector")
.connectionSelector("connSelector")
.heartbeatDelay(60_000L)
.heartbeatReplyDelay(30_000L)
.build());
*
* @author Charles W. Rapp
*
* @see ConnectionBuilder
*/
public static final class ServerBuilder
extends AbstractBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private int mPort;
private AddressFilter mAddressFilter;
private String mServiceSelector;
private String mConnSelector;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new server configuration builder.
*/
private ServerBuilder()
{
mPort = 0;
mAddressFilter = null;
mServiceSelector =
(ENetConfigure.defaultSelector()).name();
mConnSelector = mServiceSelector;
} // end of ServerBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Copies in the settings from {@code config} to this
* builder. This method is provided for making changes to
* an existing configuration since
* {@link EConfigure.Service} is immutable.
*
* if {@code config} is {@code null}, then nothing is
* done and the builder remains unchanged.
*
* @param config copy settings from this configuration.
* @return {@code this} service builder.
*/
public ServerBuilder configuration(final EConfigure.Service config)
{
if (config != null)
{
super.configuration(config);
mPort = config.port();
mAddressFilter = config.addressFilter();
mServiceSelector = config.serviceSelector();
mConnSelector = config.connectionSelector();
}
return (this);
} // end of configuration(EConfigure.Service)
/**
* Sets the service TCP port.
* @param port service TCP port.
* @return {@code this} service builder.
* @throws ConfigException
* if {@code port} is not a valid TCP port.
*/
public ServerBuilder port(final int port)
{
if (port < MIN_PORT || port > MAX_PORT)
{
throw (
new ConfigException.BadValue(
PORT_KEY,
"invalid port (" + port + ")"));
}
mPort = port;
return (this);
} // end of port(int)
/**
* Sets the optional service address filter.
* @param filter the optional address filter. May be
* {@code null}.
* @return {@code this} service builder.
*/
public ServerBuilder addressFilter(final AddressFilter filter)
{
mAddressFilter = filter;
return (this);
} // end of addressFilter(AddressFilter)
/**
* Sets the selector used for the service connection.
* @param selector eBus selector name.
* @return {@code this} service builder.
* @throws ConfigException
* if {@code name} is {@code null} or empty or is not a
* known selector.
*/
public ServerBuilder serviceSelector(final String selector)
{
if (Strings.isNullOrEmpty(selector))
{
throw (
new ConfigException.BadValue(
SVC_SELECTOR_KEY,
"service selector is null or empty"));
}
if (!ENetConfigure.isKnownSelector(selector))
{
throw (
new ConfigException.BadValue(
SVC_SELECTOR_KEY,
"\"" +
selector +
"\" unknown service selector"));
}
mServiceSelector = selector;
return (this);
} // end of serviceSelector(String)
/**
* Sets the selector used for accepted TCP connections.
* @param selector eBus selector name.
* @return {@code this} service builder.
* @throws ConfigException
* if {@code name} is {@code null} or empty or not a
* known selector.
*/
public ServerBuilder connectionSelector(final String selector)
{
if (Strings.isNullOrEmpty(selector))
{
throw (
new ConfigException.BadValue(
CONN_SELECTOR_KEY,
"connection selector is null or empty"));
}
if (!ENetConfigure.isKnownSelector(selector))
{
throw (
new ConfigException.BadValue(
CONN_SELECTOR_KEY,
"\"" +
selector +
"\" unknown connection selector"));
}
mConnSelector = selector;
return (this);
} // end of connectionSelector(String)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns the eBus service configuration built from the
* previously set parameters.
* @return an eBus service configuration.
* @throws ConfigException
* if any service name or service port is not set.
*/
public Service build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
Service.class,
problems.errors())));
}
return (new Service(this));
} // end of build()
/**
* Validates the builder parameters.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
@Override
protected Validator validate(final Validator problems)
{
return (
super.validate(problems)
.requireTrue((mPort > 0),
PORT_KEY,
"service port not set"));
} // end of validate(Validator)
} // end of class ServerBuilder
/**
* Constructs an {@link EConfigure.RemoteConnection} instance
* based on the parameters set via the builder's API. The
* minimally allowed configuration is the connection name and
* address. The remaining parameters are set to the following
* defaults:
*
* -
* connection type: {@link ConnectionType#TCP} - plain
* text TCP connection.
*
* -
* bind port: {@code ERemoteApp.ANY_PORT}.
*
* -
* input buffer size: zero - use system default buffer
* size.
*
* -
* output buffer size: zero - use system default buffer
* size.
*
* -
* byte order: little-endian.
*
* -
* maximum message queue size: zero - unlimited queue
* size.
*
* -
* connection selector thread:
* {@code AsyncChannel.defaultSelector}.
*
* -
* reconnect flag and delay: reconnection is turned off and
* delay set to zero.
*
* -
* heartbeat millisecond delay: zero - heartbeating off.
*
* -
* heartbeat millisecond reply delay: zero - wait
* indefinitely for a reply.
*
*
* Example building an {@code ERemoteApp}
* final InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 12345);
final SSLContext secureContext = ...;
final EConfigure.ConnectionBuilder builder = EConfigure.connectionBuilder();
ERemoteApp.openConnection(builder.name("Conn0")
.address(address)
.bindPort(0)
.connectionType(EConfigure.ConnectionType.SECURE_TCP)
.sslContext(secureContext)
.inputBufferSize(4_996)
.outputBufferSize(8_192)
.byteOrder(ByteOrder.BIG_ENDIAN)
.messageQueueSize(0)
.selector("connSelector")
.reconnect(true)
.reconnectDelay(500L)
.heartbeatDelay(0L)
.heartbeatReplyDelay(0L)
.build());
*
* @author Charles W. Rapp
*
* @see ServerBuilder
*/
public static final class ConnectionBuilder
extends AbstractBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private InetSocketAddress mAddress;
private int mBindPort;
private String mSelector;
private boolean mReconnectFlag;
private Duration mReconnectTime;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new eBus connection configuration builder.
*/
private ConnectionBuilder()
{
mAddress = null;
mBindPort = ANY_PORT;
mSelector = (ENetConfigure.defaultSelector()).name();
mReconnectFlag = false;
mReconnectTime = Duration.ZERO;
} // end of ConnectionBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Copies in the settings from {@code config} to this
* builder. This method is provided for making changes to
* an existing configuration since
* {@link EConfigure.RemoteConnection} is immutable.
*
* if {@code config} is {@code null}, then nothing is
* done and the builder remains unchanged.
*
* @param config copy settings from this configuration.
* @return {@code this} connection builder.
*/
public ConnectionBuilder configuration(final EConfigure.RemoteConnection config)
{
if (config != null)
{
super.configuration(config);
mAddress = config.address();
mBindPort = config.bindPort();
mSelector = config.selector();
mReconnectFlag = config.reconnectFlag();
mReconnectTime = config.reconnectTime();
}
return (this);
} // end of configuration(EConfigure.RemoteConnection)
/**
* Set the connection address.
* @param address connection address.
* @return {@code this} connection builder.
* @throws ConfigException
* if {@code address} is {@code null}.
*/
public ConnectionBuilder address(final InetSocketAddress address)
{
if (address == null)
{
throw (
new ConfigException.BadValue(
HOST_KEY, "address is null"));
}
mAddress = address;
return (this);
} // end of address(InetSocketAddress)
/**
* Bind the connection local port to this value. If
* {@code port} is set to {@link ENetConfigure#ANY_PORT},
* then the connection local port is set to any available
* value.
* @param port connection local bind port.
* @return {@code this} connection builder.
* @throws ConfigException
* if {@code port} is either <
* {@link ENetConfigure#ANY_PORT} or >
* {@link ENetConfigure#MAX_PORT}.
*/
public ConnectionBuilder bindPort(final int port)
{
if (port < ANY_PORT || port > MAX_PORT)
{
throw (
new ConfigException.BadValue(
BIND_PORT_KEY,
"invalid bind port (" + port + ")"));
}
mBindPort = port;
return (this);
} // end of bindPort(int)
/**
* Sets the selector used for the connection.
* @param selector eBus selector name.
* @return {@code this} connection builder.
* @throws ConfigException
* if {@code name} is {@code null}, an empty string or
* not a known selector.
*/
public ConnectionBuilder selector(final String selector)
{
if (Strings.isNullOrEmpty(selector))
{
throw (
new ConfigException.BadValue(
SELECTOR_KEY,
"selector is null or empty"));
}
if (!ENetConfigure.isKnownSelector(selector))
{
throw (
new ConfigException.BadValue(
SELECTOR_KEY,
"\"" + selector + "\" unknown selector"));
}
mSelector = selector;
return (this);
} // end of selector(String)
/**
* Sets the reconnect flag to the given value.
* {@code true} means the connection will be
* re-established if lost; {@code false} means a lost
* connection is left down.
* @param flag establish lost connection flag.
* @return {@code this} connection builder.
*
* @see #reconnectDelay(Duration)
*/
public ConnectionBuilder reconnect(final boolean flag)
{
mReconnectFlag = flag;
return (this);
} // end of reconnect(boolean)
/**
* Sets the reconnect delay to the given value. This value
* is used on if the reconnect flag is {@code true}.
* @param time reconnect a lost connect after this
* millisecond delay.
* @return {@code this} connection builder.
* @throws ConfigException
* if {@code time} is {@code null } or < zero.
*
* @see #reconnect(boolean)
*/
public ConnectionBuilder reconnectDelay(final Duration time)
{
if (time == null)
{
throw (
new ConfigException.BadValue(
RECONNECT_DELAY_KEY,
"reconnect time is null"));
}
if (time.isNegative())
{
throw (
new ConfigException.BadValue(
RECONNECT_DELAY_KEY,
"reconnect time < 0"));
}
mReconnectTime = time;
return (this);
} // end of reconnectDelay(long)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns the eBus connection configuration built from
* the previously set parameters.
* @return an eBus connection configuration.
* @throws ConfigException
* if either connection name or address is not set or if
* reconnect flag is set to {@code true} but the
* reconnect delay is not set.
*/
public RemoteConnection build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
RemoteConnection.class,
problems.errors())));
}
return (new RemoteConnection(this));
} // end of build()
/**
* Validates the builder parameters.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
@Override
protected Validator validate(final Validator problems)
{
return (
super.validate(problems)
.requireNotNullOrEmpty(mName, NAME_KEY)
.requireNotNull(mAddress, HOST_KEY)
.requireTrue((!mReconnectFlag ||
mReconnectTime.compareTo(
Duration.ZERO) > 0),
RECONNECT_DELAY_KEY,
"reconnect time not set"));
} // end of validate()
} // end of class ConnectionBuilder
/**
* Constructs an {@link EConfigure.Dispatcher} configuration
* instance based on the parameters set via the builder's
* API. The minimally allowed configuration is the Dispatcher
* name and Dispatcher type if that type is either
* {@link DispatcherType#SWING} or
* {@link DispatcherType#JAVAFX}. For a
* {@link DispatcherType#EBUS} Dispatcher type, then the
* thread type must be provided. If this Dispatcher is
* not marked as the default, then the classes
* assigned to this Dispatcher must be provided.
*
* The remaining properties are set to the following
* defaults:
*
*
* -
* spin limit: {@link EConfigure#DEFAULT_SPIN_LIMIT}.
*
* -
* park time: {@link EConfigure#DEFAULT_PARK_TIME}.
*
* -
* priority: {@link Thread#NORM_PRIORITY}.
*
* -
* quantum: {@link EConfigure#DEFAULT_QUANTUM}.
*
* -
* thread count: {@link EConfigure#DEFAULT_NUMBER_THREADS}.
*
* -
* default Dispatcher: {@code false}.
*
* -
* thread affinity: {@code null}.
*
*
*
* Note: there is no example of how to
* build a {@code Dispatcher} in application code because
* eBus dispatchers must be created on system start. Once the
* application {@code main} method is reached, it is too late
* to create a dispatcher.
*
*
* @author Charles W. Rapp
*/
public static final class DispatcherBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private String mName;
private DispatcherType mType;
private ThreadType mRunQueueType;
private long mSpinLimit;
private Duration mParkTime;
private int mPriority;
private Duration mQuantum;
private int mNumThreads;
private boolean mIsDefault;
private Class>[] mClasses;
private ThreadAffinityConfigure[] mAffinity;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a new dispatcher builder instance used to
* construct an {@link EConfigure.Dispatcher} instance.
*/
private DispatcherBuilder()
{
mName = null;
mType = null;
mRunQueueType = null;
mSpinLimit = 0L;
mParkTime = Duration.ZERO;
mPriority = Thread.NORM_PRIORITY;
mQuantum = EConfigure.DEFAULT_QUANTUM;
mNumThreads = EConfigure.DEFAULT_NUMBER_THREADS;
mIsDefault = false;
mClasses = new Class>[0];
} // end of DispatcherBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Copies in the settings from {@code config} to this
* builder. This method is provided for making changes to
* an existing configuration since
* {@link EConfigure.Dispatcher} is immutable.
*
* if {@code config} is {@code null}, then nothing is
* done and the builder remains unchanged.
*
* @param config copy settings from this configuration.
* @return {@code this} dispatcher builder.
*/
public DispatcherBuilder configuration(final EConfigure.Dispatcher config)
{
if (config != null)
{
mName = config.name();
mType = config.dispatchType();
mRunQueueType = config.runQueueType();
mSpinLimit = config.spinLimit();
mParkTime = config.parkTime();
mPriority = config.priority();
mQuantum = config.quantum();
mNumThreads = config.numberThreads();
mIsDefault = config.isDefault();
mClasses = config.classes();
}
return (this);
} // end of configuration(final EConfigure.Dispatcher config)
/**
* Sets the dispatcher name.
* @param name dispatcher name.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code name} is null or empty.
*/
public DispatcherBuilder name(final String name)
{
if (Strings.isNullOrEmpty(name))
{
throw (
new ConfigException.BadValue(
NAME_KEY, INVALID_NAME));
}
mName = name;
return (this);
} // end of name(String)
/**
* Sets the dispatcher type.
* @param type dispatcher type.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code type} is {@code null} or an unknown
* dispatcher type.
*/
public DispatcherBuilder dispatcherType(final DispatcherType type)
{
if (type == null)
{
throw (
new ConfigException.BadValue(
DISPATCHER_TYPE_KEY,
"type is null or unknown"));
}
mType = type;
return (this);
} // end of dispatcherType(DispatcherType)
/**
* Sets the run queue thread type. This setting is
* ignored if the Dispatcher type is
* {@link DispatcherType#SWING} or
* {@link DispatcherType#JAVAFX}.
* @param type thread type.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code type} is {@code null} or an unknown thread
* type.
*/
public DispatcherBuilder threadType(final ThreadType type)
{
if (type == null)
{
throw (
new ConfigException.BadValue(
THREAD_TYPE_KEY, "type is null or unknown"));
}
mRunQueueType = type;
return (this);
} // end of threadType(ThreadType)
/**
* Sets the {@link ThreadType#SPINPARK} or
* {@link ThreadType#SPINYIELD} spin limit. This setting
* is ignored for any other run queue thread type.
* @param limit spin limit.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code limit} < zero.
*/
public DispatcherBuilder spinLimit(final long limit)
{
if (limit < 0L)
{
throw (
new ConfigException.BadValue(
SPIN_LIMIT_KEY, "limit < zero"));
}
mSpinLimit = limit;
return (this);
} // end of spinLimit(long)
/**
* Sets the {@link ThreadType#SPINPARK} park time limit.
* This setting is ignored for any other run queue thread
* type.
* @param time park time limit.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code time} is {@code null} or < zero.
*/
public DispatcherBuilder parkTime(final Duration time)
{
if (time == null)
{
throw (
new ConfigException.BadValue(
PARK_TIME_KEY, "time is null"));
}
if (time.isNegative())
{
throw (
new ConfigException.BadValue(
PARK_TIME_KEY, "time < zero"));
}
mParkTime = time;
return (this);
} // end of parkTime(Duration)
/**
* Sets the run queue thread priority. Must be ≥
* {@link Thread#MIN_PRIORITY} and ≤
* {@link Thread#MAX_PRIORITY}. This setting is ignored
* if the Dispatcher type is {@link DispatcherType#SWING}
* or {@link DispatcherType#JAVAFX}.
* @param priority assigned thread priority for run queue
* threads.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code priority} < zero or >
* {@code Thread.MAX_PRIORITY}.
*/
public DispatcherBuilder priority(final int priority)
{
if (priority < 0 || priority > Thread.MAX_PRIORITY)
{
throw (
new ConfigException.BadValue(
PRIORITY_KEY, "priority out of bounds"));
}
mPriority = priority;
return (this);
} // end of priority(int)
/**
* Sets the run quantum assigned to each eBus client.
* @param quantum run quantum.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code quantum} is {@code null} or < zero.
*/
public DispatcherBuilder quantum(final Duration quantum)
{
if (quantum == null)
{
throw (
new ConfigException.BadValue(
QUANTUM_KEY, "quantum is null"));
}
if (quantum.isNegative())
{
throw (
new ConfigException.BadValue(
QUANTUM_KEY, "quantum < zero"));
}
mQuantum = quantum;
return (this);
} // end of quantum(Duration)
/**
* Sets the number of threads used by the run queue. This
* setting is ignored if the Dispatcher type is
* {@link DispatcherType#SWING} or
* {@link DispatcherType#JAVAFX}.
* @param numThreads number of run queue threads.
* @return {@code this} dispatcher builder.
* @throws ConfigException
* if {@code numThreads} < zero.
*/
public DispatcherBuilder numberThreads(final int numThreads)
{
if (numThreads < 0)
{
throw (
new ConfigException.BadValue(
NUM_THREADS_KEY, "numThreads < zero"));
}
mNumThreads = numThreads;
return (this);
} // end of numberThreads(int)
/**
* If {@code flag} is {@code true}, then marks the
* dispatcher as the default dispatcher.
* @param flag {@code true} marks the dispatcher as the
* default.
* @return {@code this} dispatcher builder.
*/
public DispatcherBuilder isDefault(final boolean flag)
{
mIsDefault = flag;
return (this);
} // end of isDefault(boolean)
/**
* Lists the classes assigned to the dispatcher. If the
* default dispatcher, this list is ignored and
* {@code classes} may be {@code null} or empty. If the
* dispatcher is not default, then
* {@code classes} must be defined as a non-{@code null},
* non-empty array.
*
* This parameter is checked when the dispatcher is
* {@link #build() built} since the validation depends on
* whether the dispatcher is default or not.
*
* @param classes eBus client classes assigned to the
* Dispatcher.
* @return {@code this} dispatcher builder.
*/
public DispatcherBuilder classes(final Class>[] classes)
{
if (classes == null)
{
mClasses = new Class>[0];
}
else
{
mClasses =
Arrays.copyOf(classes, classes.length);
}
return (this);
} // end of classes(Class>)
/**
* Sets optional thread affinity to the given
* configuration list . Thread affinity should be
* considered when using spinning thread type.
* @param affinities thread affinity configuration.
* May be {@code null} or empty.
* @return {@code this} dispatcher builder.
*/
public DispatcherBuilder threadAffinity(final List affinities)
{
return (
threadAffinity(
affinities.toArray(
new ThreadAffinityConfigure[
affinities.size()])));
} // end of threadAffinity(List<>)
/**
* Sets optional thread affinity to the given
* configuration list . Thread affinity should be
* considered when using spinning thread type.
* @param affinities thread affinity configuration.
* May be {@code null} or empty.
* @return {@code this} dispatcher builder.
*/
public DispatcherBuilder threadAffinity(final ThreadAffinityConfigure[] affinities)
{
mAffinity = affinities;
return (this);
} // end of threadAffinity(ThreadAffinityConfigure[])
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns the eBus Dispatcher configuration built from
* the previously set parameters.
* @return an eBus Dispatcher configuration.
* @throws ConfigException
* if either the Dispatcher name, Dispatcher type, or run
* queue thread type are not set, or if the Dispatcher is
* not marked as default and no classes were provided.
*/
public EConfigure.Dispatcher build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
Dispatcher.class,
problems.errors())));
}
return (new Dispatcher(this));
} // end of build()
/**
* Validates the {@code Dispatcher} builder parameters.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
private Validator validate(final Validator problems)
{
problems.requireNotNull(mName, NAME_KEY)
.requireNotNull(mType,
DISPATCHER_TYPE_KEY)
.requireNotNull(mRunQueueType,
THREAD_TYPE_KEY)
.requireTrue(
(mRunQueueType != ThreadType.SPINPARK ||
mSpinLimit > 0),
SPIN_LIMIT_KEY,
"spin limit not set for spin+park thread type")
.requireTrue(
(mRunQueueType != ThreadType.SPINPARK ||
(mParkTime != null &&
mParkTime.compareTo(Duration.ZERO) > 0)),
PARK_TIME_KEY,
"park limit not set for spin+park thread type")
.requireTrue(
(mRunQueueType != ThreadType.SPINYIELD ||
mSpinLimit > 0L),
SPIN_LIMIT_KEY,
"spin limit not set for spin+yield thread type")
.requireTrue(
(mIsDefault ||
(mClasses != null &&
mClasses.length > 0)),
CLASSES_KEY,
"classes not set for non-default dispatcher");
// Are thread affinities used?
if (mAffinity != null && mAffinity.length > 0)
{
// Yes. Check those separately.
validateAffinities(problems);
}
return (problems);
} // end of validate()
/**
* Validates thread affinity settings.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
private Validator validateAffinities(final Validator problems)
{
return (
problems.requireTrue(
mAffinity[0].affinityType() !=
AffinityType.CPU_STRATEGIES,
CLASSES_KEY,
"may not use strategy thread affinity first")
.requireTrue(
(mAffinity.length == mNumThreads ||
mAffinity[mAffinity.length - 1].affinityType() !=
AffinityType.CPU_ID),
CLASSES_KEY,
"may not use CPU ID thread affinity last"));
}
} // end of class DispatcherBuilder
/**
* Constructs a {@link PauseConfig} instance based on the
* parameters set via the builder API. Configuration settings
* are based on the connection role. Pause duration and
* maximum backlog size may be set for both connection
* acceptor and initiator. The remaining settings (discard
* policy, idle time, maximum connection time, and
* resume-on-backlog-size) may only be set for initiators.
*
* Properties depend on the connection role. For acceptor,
* only the pause duration (required) and maximum backlog
* size (optional) may be set. For initiator, all properties
* may be used where pause duration and maximum connect time
* are required and the remaining properties are optional.
*
* Example building a {@link PauseConfig} - acceptor
* final EConfigure.PauseBuilder builder = new EConfigure.pauseBuilder(EConfigure.ConnectionRole.Acceptor);
* final EConfigure.PauseConfig pauseConfig = builder.duration(Duration.ofMinutes(5L)
* .maxBacklogSize(10)
* .build();
*
* Please note that the connection acceptor uses the discard
* policy set by the connection initiator.
*
* Example building a {@link PauseConfig} - initiator
* final EConfigure.PauseBuilder builder = new EConfigure.pauseBuilder(EConfigure.ConnectionRole.Initiator);
* final EConfigure.PauseConfig pauseConfig = builder.duration(Duration.ofMinutes(5L)
* .maxBacklogSize(20)
* .discardPolicy(EConfigure.DiscardPolicy.OLDEST_FIRST)
* .maxConnectionTime(Duration.ofSeconds(30L))
* .resumeOnBacklogSize(5)
* .build();
*/
public static final class PauseBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
/**
* The role defines which pause properties are required,
* optional, and not allowed.
*/
private final ConnectionRole mRole;
/**
* If {@link #mCanPause} is {@code true}, then this is
* the maximum allowed pause duration. Once this duration
* is reached the connection initiator attempts to
* resume the connection.
*
* This value must be set for both connection acceptor
* and initiator.
*
*/
private Duration mDuration;
/**
* If {@link #mCanPause} is {@code true}, then this is
* the maximum allowed message backlog size. If set to
* zero then the message backlog size is unlimited.
*/
private int mMaxBacklogSize;
/**
* If {@link #mCanPause} is {@code true}, then this is
* the message discard policy used when
* {@link #mMaxBacklogSize} is breached.
*/
private DiscardPolicy mDiscardPolicy;
/**
* If a remote connection does not send or receive any
* messages for this duration, the connection is paused.
*/
private Duration mIdleTime;
/**
* A remote connection remains connected for this
* duration at most. Once this time limit is reached, the
* connection is automatically paused - independent of
* when the last message was sent or received.
*/
private Duration mMaxConnectTime;
/**
* Resume when transmit queue reaches this limit. This
* can only be true for the client side.
*/
private int mResumeOnBacklogSize;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates a pause configuration builder for the
* specified connection role.
* @param role either a connection acceptor or a
* connection initiator.
*/
private PauseBuilder(final ConnectionRole role)
{
mRole = role;
mDuration = Duration.ZERO;
mMaxBacklogSize = 0;
mIdleTime = Duration.ZERO;
mMaxConnectTime = Duration.ZERO;
mResumeOnBacklogSize = 0;
} // end of PauseBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets the pause duration and returns {@code this}
* pause configuration builder. This value must be set
* for both acceptor and initiator connections.
* @param duration pause duration.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if {@code duration} is {@code null} or is ≤ zero.
*/
public PauseBuilder duration(final Duration duration)
{
if (duration == null)
{
throw (
new ConfigException.BadValue(
PAUSE_DURATION_KEY, INVALID_DURATION_NULL));
}
if (duration.compareTo(Duration.ZERO) <= 0)
{
throw (
new ConfigException.BadValue(
PAUSE_DURATION_KEY, INVALID_DURATION));
}
mDuration = duration;
return (this);
} // end of duration(final Duration duration)
/**
* Sets the maximum backlog size and returns {@code this}
* pause configuration builder. This value may be set
* for both acceptor and initiator connections.
* @param size maximum backlog size.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if {@code size} is < zero.
*/
public PauseBuilder maxBacklogSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
MAX_BACKLOG_SIZE_KEY, "size < 0"));
}
mMaxBacklogSize = size;
return (this);
} // end of maxBacklogSize(final int size)
/**
* Sets the backlog discard policy and returns
* {@code this} pause configuration builder. This value
* may only be set for initiator connections.
* @param policy backlog overflow discard policy.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if this is a acceptor connection or {@code policy} is
* {@code null}.
*/
public PauseBuilder discardPolicy(final DiscardPolicy policy)
{
if (mRole == ConnectionRole.ACCEPTOR)
{
throw (
new ConfigException.Generic(
"cannot set discard policy for acceptor connection"));
}
if (policy == null)
{
throw (
new ConfigException.BadValue(
DISCARD_POLICY_KEY, "policy is null"));
}
mDiscardPolicy = policy;
return (this);
} // end of discardPolicy(final DiscardPolicy policy)
/**
* Sets the idle time duration and returns {@code this}
* pause configuration builder. This value may only be
* set for initiator connections.
* @param duration maximum idle time duration.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if this is a acceptor connection or {@code duration}
* is {@code null} or is ≤ zero.
*/
public PauseBuilder idleTime(final Duration duration)
{
if (mRole == ConnectionRole.ACCEPTOR)
{
throw (
new ConfigException.Generic(
"cannot set idle time for acceptor connection"));
}
if (duration == null)
{
throw (
new ConfigException.BadValue(
IDLE_TIME_KEY, INVALID_DURATION_NULL));
}
if (duration.compareTo(Duration.ZERO) <= 0)
{
throw (
new ConfigException.BadValue(
IDLE_TIME_KEY, INVALID_DURATION));
}
mIdleTime = duration;
return (this);
} // end of idleTime(final Duration duration)
/**
* Sets the maximum connection time duration and
* returns {@code this} pause configuration builder.
* This value may only be set for initiator connections.
* @param duration maximum connection time duration.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if this is a acceptor connection or {@code duration}
* is {@code null} or is ≤ zero.
*/
public PauseBuilder maxConnectionTime(final Duration duration)
{
if (mRole == ConnectionRole.ACCEPTOR)
{
throw (
new ConfigException.Generic(
"cannot set max connection time for acceptor connection"));
}
if (duration == null)
{
throw (
new ConfigException.BadValue(
MAX_CONNECT_TIME_KEY,
INVALID_DURATION_NULL));
}
if (duration.compareTo(Duration.ZERO) <= 0)
{
throw (
new ConfigException.BadValue(
MAX_CONNECT_TIME_KEY, INVALID_DURATION));
}
mMaxConnectTime = duration;
// If idle time is not set, then set that time to
// the max connection time.
if (mIdleTime.equals(Duration.ZERO))
{
mIdleTime = duration;
}
return (this);
} // end of maxConnectionTime(final Duration duration)
/**
* Sets the transmit queue limit which trigger automatic
* client connection resumption. If {@code limit} is zero
* then this feature is disabled. Note: the transmit
* queue size is based on the number of queued
* application messages only.
*
* This value may only be set for initiator connections.
*
* @param size transmit queue size trigger connection
* resumption.
* @return {@code this PauseBuilder}.
* @throws ConfigException
* if this is a acceptor connection or {@code size}
* < zero.
*/
public PauseBuilder resumeOnBacklogSize(final int size)
{
if (mRole == ConnectionRole.ACCEPTOR)
{
throw (
new ConfigException.Generic(
"cannot set max connection time for acceptor connection"));
}
if (size < 0)
{
throw (
new ConfigException.BadValue(
RESUME_ON_BACKLOG_SIZE_KEY, "size < 0"));
}
mResumeOnBacklogSize = size;
return (this);
} // end of resumeOnBacklogSize(int)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns the pause configuration created from the
* configured parameters.
* @return a pause configuration instance.
* @throws ConfigException
* if the pause configuration contains errors.
*/
public PauseConfig build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
PauseConfig.class,
problems.errors())));
}
return (new PauseConfig(this));
} // end of build()
/**
* Validates the pause configuration setting before
* building said configuration.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
private Validator validate(final Validator problems)
{
return (
problems.requireTrue(!mDuration.isZero(),
PAUSE_DURATION_KEY,
"pause duration not set")
.requireTrue(
(mRole != ConnectionRole.INITIATOR ||
!mMaxConnectTime.isZero()),
MAX_CONNECT_TIME_KEY,
"maximum connect time not set"));
} // end of validate()
} // end of class PauseBuilder
/**
* Builder used to created a {@link MulticastConnection}
* connection instance. A builder instance can be obtained
* by calling {@link EConfigure#multicastBuilder()}.
*
* See
* {@code net.sf.eBus.client.EMulticastConnection} for
* detailed explanation on how to configure a multicast
* connection.
*
*
* @see MulticastConnection
* @see EConfigure#multicastBuilder()
*/
public static final class MulticastBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private String mName;
private MulticastRole mRole;
private InetAddress mGroup;
private int mTargetPort;
private NetworkInterface mNetworkIF;
private List mSources;
private InetAddress mBindAddress;
private int mBindPort;
private ProtocolFamily mFamily;
private ByteOrder mByteOrder;
private String mSelector;
private int mInputBufferSize;
private int mOutputBufferSize;
private List mNotifications;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Use {@link EConfigure#multicastBuilder()} to obtain
* an instance of a multicast builder.
*/
private MulticastBuilder()
{
mTargetPort = -1;
mBindPort = ANY_PORT;
mByteOrder = DEFAULT_BYTE_ORDER;
mSelector = (ENetConfigure.defaultSelector()).name();
mInputBufferSize = 0;
mOutputBufferSize = 0;
} // end of MulticastBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets the multicast connection name. Name must be
* unique within the JVM.
*
* This setting is required.
*
* @param name multicast connection name.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code name} is {@code null} or empty.
*/
public MulticastBuilder name(final String name)
{
if (Strings.isNullOrEmpty(name))
{
throw (
new ConfigException.BadValue(
NAME_KEY, INVALID_NAME));
}
mName = name;
return (this);
} // end of name(String)
/**
* Sets the multicast connection role to either publisher
* or subscriber.
*
* This setting is required.
*
* @param role multicast connection role.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code role} is {@code null}.
*/
public MulticastBuilder role(final MulticastRole role)
{
if (role == null)
{
throw (
new ConfigException.BadValue(
MULTICAST_ROLE_KEY,
"role is null"));
}
mRole = role;
return (this);
} // end of role(MulticastRole)
/**
* Set multicast group address.
*
* This setting is required.
*
* @param group multicast group address.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code group} is {@code null} or not a multicast
* address.
*/
public MulticastBuilder group(final InetAddress group)
{
if (group == null)
{
throw (
new ConfigException.BadValue(
GROUP_KEY,
"group is null"));
}
if (!group.isMulticastAddress())
{
throw (
new ConfigException.BadValue(
GROUP_KEY,
String.format(
"\"%s\" is not a multicast address",
group)));
}
mGroup = group;
return (this);
} // end of group(InetAddress)
/**
* Set multicast group target port. This port is only
* used when
* {@link java.nio.channels.DatagramChannel#send(java.nio.ByteBuffer, java.net.SocketAddress) posting}
* messages to the group.
*
* This setting is required.
*
* @param port multicast group port.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code port} is outside the range
* [{@link ENetConfigure#MIN_PORT},
* {@link ENetConfigure#MAX_PORT}]
* inclusive.
*/
public MulticastBuilder targetPort(final int port)
{
if (port < MIN_PORT || port > MAX_PORT)
{
throw (
new ConfigException.BadValue(
BIND_PORT_KEY,
"invalid target port (" + port + ")"));
}
mTargetPort = port;
return (this);
} // end of targetPort(int)
/**
* Sets the network interface associated with the
* multicast group.
*
* This setting is required.
*
* @param netIf multicast group network interface.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code netIf} is {@code null}.
*/
public MulticastBuilder networkInterface(final NetworkInterface netIf)
{
if (netIf == null)
{
throw (
new ConfigException.BadValue(
NET_IF_KEY,
"networkInterface is null"));
}
mNetworkIF = netIf;
return (this);
} // end of networkInterface(NetworkInterface)
/**
* Optional set of multicast source addresses. If
* configured then only those messages pasted from the
* given source addresses are accepted. Does nothing if
* {@code sources} is either {@code null} or empty.
*
* This setting is optional.
*
* @param sources accepted multicast message source
* addresses. May be {@code null} or empty.
* @return {@code this} multicast connection builder.
*/
public MulticastBuilder sources(final List sources)
{
if (sources == null || sources.isEmpty())
{
mSources = ImmutableList.of();
}
else
{
mSources = ImmutableList.copyOf(sources);
}
return (this);
} // end of sources(List<>)
/**
* Bind socket to given local address. If address is
* {@code null} then socket is bound to an automatically
* assigned address.
*
* This setting is optional.
*
* @param address bind socket local side to this address.
* May be {@code null}.
* @return {@code this} multicast connection builder.
*/
public MulticastBuilder bindAddress(final InetAddress address)
{
mBindAddress = address;
return (this);
} // end of bindAddress(InetAddress)
/**
* Bind the multicast connection local port to this
* value. If {@code port} is set to
* {@link ENetConfigure#ANY_PORT}, then the connection
* local port is set to any available value.
*
* This setting is optional. Default setting is
* {@link ENetConfigure#ANY_PORT}.
*
* @param port multicast connection local bind port.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code port} is either <
* {@link ENetConfigure#ANY_PORT} or >
* {@link ENetConfigure#MAX_PORT}.
*/
public MulticastBuilder bindPort(final int port)
{
if (port < ANY_PORT || port > MAX_PORT)
{
throw (
new ConfigException.BadValue(
BIND_PORT_KEY,
"invalid bind port (" + port + ")"));
}
mBindPort = port;
return (this);
} // end of bindPort(int)
/**
* Sets the multicast group protocol family.
*
* This setting is required.
*
* @param family multicast protocol family. Must match
* {@link #group(InetAddress)}.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code family} is {@code null}.
*/
public MulticastBuilder protocolFamily(final ProtocolFamily family)
{
if (family == null)
{
throw (
new ConfigException.BadValue(
PROTOCOL_KEY,
"protocolFamily is null"));
}
mFamily = family;
return (this);
} // end of protocolFamily(ProtocolFamily)
/**
* Sets the byte order used by the multicast connection.
*
* This setting is optional. Default setting is
* {@link EConfigure#DEFAULT_BYTE_ORDER}.
*
* @param byteOrder multicast connection serialize and
* de-serialize messages using this byte order.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code byteOrder} is {@code nuill}.
*/
public MulticastBuilder byteOrder(final ByteOrder byteOrder)
{
if (byteOrder == null)
{
throw (
new ConfigException.BadValue(
BYTE_ORDER_KEY, "byteOrder is null"));
}
mByteOrder = byteOrder;
return (this);
} // end of byteOrder(ByteOrder)
/**
* Sets the selector used for the multicast connection.
*
* This setting is optional. Default setting is
* {@link ENetConfigure#defaultSelector()}.
*
* @param selector eBus selector name.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code name} is {@code null}, an empty string or
* not a known selector.
*/
public MulticastBuilder selector(final String selector)
{
if (Strings.isNullOrEmpty(selector))
{
throw (
new ConfigException.BadValue(
SELECTOR_KEY,
"selector is null or empty"));
}
if (!ENetConfigure.isKnownSelector(selector))
{
throw (
new ConfigException.BadValue(
SELECTOR_KEY,
"\"" + selector + "\" unknown selector"));
}
mSelector = selector;
return (this);
} // end of selector(String)
/**
* Sets the input buffer size for the multicast
* connection. If {@code size} is zero, then the default
* input buffer size is used.
*
* This setting is optional.
*
* @param size multicast connection input buffer size.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code size} < zero.
*/
public MulticastBuilder inputBufferSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
INBUFFER_SIZE_KEY,
"input buffer size < 0"));
}
mInputBufferSize = size;
return (this);
} // end of inputBufferSize(int)
/**
* Sets the output buffer size for the multicast
* connection. If {@code size} is zero, then the default
* output buffer size is used.
*
* This setting is optional.
*
* @param size multicast connection output buffer size.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code size} < zero.
*/
public MulticastBuilder outputBufferSize(final int size)
{
if (size < 0)
{
throw (
new ConfigException.BadValue(
OUTBUFFER_SIZE_KEY,
"output buffer size < 0"));
}
mOutputBufferSize = size;
return (this);
} // end of outputBufferSize(int)
/**
* Set notification message key configurations.
*
* This setting is required.
*
* @param notifications notification message keys.
* @return {@code this} multicast connection builder.
* @throws ConfigException
* if {@code notifications} is either {@code null} or
* empty.
*/
public MulticastBuilder notifications(final List notifications)
{
if (notifications == null)
{
throw (
new ConfigException.BadValue(
NOTIFICATION_KEY,
"notifications is null"));
}
if (notifications.isEmpty())
{
throw (
new ConfigException.BadValue(
NOTIFICATION_KEY,
"notifications is empty"));
}
mNotifications = ImmutableList.copyOf(notifications);
return (this);
} // end of notifications(List<>)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns {@code MulticastConnection} configured as per
* this builder's current settings.
* @return {@link MulticastConnection} configured as per
* this builder's current settings.
* @throws ConfigException
* if the current builder settings are not valid.
*/
public MulticastConnection build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
MulticastConnection.class,
problems.errors())));
}
return (new MulticastConnection(this));
} // end of build()
/**
* Validates the current builder settings prior to
* instantiating the multicast configuration.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
private Validator validate(final Validator problems)
{
return (
problems.requireNotNull(mName, NAME_KEY)
.requireNotNull(mRole,
MULTICAST_ROLE_KEY)
.requireNotNull(mGroup, GROUP_KEY)
.requireTrue((mTargetPort >= 0),
TARGET_PORT_KEY,
"multicast target port not set")
.requireNotNull(mNetworkIF, NET_IF_KEY)
.requireNotNull(mFamily, PROTOCOL_KEY)
.requireNotNull(mNotifications,
NOTIFICATION_KEY));
} // end of validate()
} // end of class MulticastBuilder
/**
* Builder used to create {@link McastNotifyConfig}
* instances. The single class constructor is {@code private}
* so a class instance is acquired by calling
* {@link EConfigure#notificationBuilder()}.
*/
public static final class McastNotifyBuilder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private MultifeedType mType;
private String mMessageClass;
private List mSubjectList;
private Pattern mSubjectQuery;
private boolean mIsDynamic;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* {@code McastNotifyBuilder} instance can be obtained by
* calling {@link EConfigure#notificationBuilder()}.
*/
private McastNotifyBuilder()
{
mSubjectList = null;
mSubjectQuery = null;
mIsDynamic = false;
} // end of McastNotifyBuilder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets notification multi-feed type to either
* {@code LIST} or {@code QUERY}. This setting determines
* whether {@code subjectList} or {@code subjectQuery}
* method is used.
*
* This setting is required.
*
* @param type multi-feed type.
* @return {@code this McastNotifyBuilder} instance.
* @throws ConfigException
* if {@code type} is {@code null}.
*/
public McastNotifyBuilder feedType(final MultifeedType type)
{
if (type == null)
{
throw (
new ConfigException.BadValue(
MULTIFEED_TYPE_KEY,
"multi-feed type is null"));
}
mType = type;
return (this);
} // end of feedType(MultifeedType)
/**
* Sets the notification class and returns {@code this}
* multicast notification builder.
*
* This setting is required for both list and query
* multi-feed types.
*
* @param name message class name.
* @return {@code this McastNotifyBuilder} instance.
* @throws ConfigException
* if {@code name} is either {@code null} or an empty
* string.
*/
public McastNotifyBuilder messageClass(final String name)
{
if (Strings.isNullOrEmpty(name))
{
throw (
new ConfigException.BadValue(
MESSAGE_CLASS_KEY,
"message class name is either null or an empty string"));
}
mMessageClass = name;
return (this);
} // end of messageClass(String)
/**
* Sets multi-feed subjects list. This value is ignored
* if multi-feed type is {@code QUERY}.
*
* This setting is required if multicast feed
* type is set to {@link MultifeedType#LIST}.
*
* @param subjects multi-feed subjects list.
* @return {@code this McastNotifyBuilder} instance.
* @throws ConfigException
* if {@code subjects} is either {@code null} or an empty
* list.
*/
public McastNotifyBuilder subjectList(final List subjects)
{
if (subjects == null || subjects.isEmpty())
{
throw (
new ConfigException.BadValue(
MESSAGE_CLASS_KEY,
"subjects is either null or an empty list"));
}
mSubjectList = new ArrayList<>(subjects);
return (this);
} // end of subjectList(List<>)
/**
* Sets the subject query and returns {@code this}
* multicast notification builder. This value is ignored
* if multi-feed type is {@code LIST}.
*
* This setting is required if multicast feed
* type is set to {@link MultifeedType#QUERY}.
*
* @param query subject query pattern.
* @return {@code this McastNotifyBuilder} instance.
* @throws ConfigException
* if {@code query} is not a valid {@link Pattern}.
*/
public McastNotifyBuilder subjectQuery(final String query)
{
try
{
mSubjectQuery = Pattern.compile(query);
}
catch (IllegalArgumentException argex)
{
throw (
new ConfigException.BadValue(
SUBJECT_QUERY_KEY,
"\"" + query + "\" is not a valid subject query",
argex));
}
return (this);
} // end of subjectQuery(String)
/**
* Sets dynamic multi-feed flag to given value. This
* value is ignored if multi-feed type is {@code LIST}.
*
* This setting is optional and may be called only if
* the multicast feed type is {@link MultifeedType#QUERY}.
* Default setting is {@code false}.
*
* @param flag turn dynamic feed on or off.
* @return {@code this McastNotifyBuilder} instance.
*/
public McastNotifyBuilder isDynamic(final boolean flag)
{
mIsDynamic = flag;
return (this);
} // end of isDynamic(boolean)
//
// end of Set Methods.
//-------------------------------------------------------
/**
* Returns a new multicast notification configuration
* created from the builder settings.
* @return multicast notification configuration.
* @throws ConfigException
* if builder settings are invalid.
*/
public McastNotifyConfig build()
{
final Validator problems =
validate(new Validator());
// Were any problems found?
if (!problems.isEmpty())
{
// Yes. Create a config exception based on those
// problems.
throw (
new ConfigException.Generic(
"invalid service configuration",
new ValidationException(
McastNotifyConfig.class,
problems.errors())));
}
// Clear any inappropriately set data members.
if (mType == MultifeedType.LIST)
{
mSubjectQuery = null;
mIsDynamic = false;
}
else
{
mSubjectList = null;
}
return (new McastNotifyConfig(this));
} // end of build()
/**
* Verifies that this builder contains a valid
* multicast notification builder.
* This validation is "fail slow" meaning that a single
* validation call will determine all configuration
* errors.
* @param problems record configuration problems in this
* list.
* @return {@code problems} so {@code validate} calls
* may be chained together.
*/
private Validator validate(final Validator problems)
{
return (
problems.requireNotNull(mMessageClass,
MESSAGE_CLASS_KEY)
.requireTrue(
(mType != MultifeedType.LIST ||
mSubjectList != null),
SUBJECT_LIST_KEY,
"subject list not set")
.requireTrue(
(mType != MultifeedType.QUERY ||
mSubjectQuery != null),
SUBJECT_QUERY_KEY,
"subject query not set"));
} // end of validate()
} // end of class McastNotifyBuilder
} // end of class EConfigure