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

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.annotation.Nullable;
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
PropertyRequired?TypeDefaultDescription
{@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 */ @SuppressWarnings ({"java:S1067"}) 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 an unsecured 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), /** * Remote application connection is a reliable, unsecured * UDP socket */ RELIABLE_UDP (false, false, null), /** * Remote application connection is a reliable, secured * UDP socket. */ SECURE_RELIABLE_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 /** * Defines supported eBus message compilers. An eBus message * compiler is used to generate a {@code DataType} class at * run time. This class is used to serialize and de-serialize * eBus message objects. */ public enum MessageCompilerType { /** * Generates code for a new {@code DataType} subclass * which serializes and de-serializes an eBus message * object. This is the default compiler type. */ JAVA_ASSIST, /** * Creates a {@code DataType} subclasses which uses * {@code java.lang.invoke} package to serialize, * de-serialize eBus message objects. This method should * be used only when dynamic Java compilation is not * possible. */ JAVA_INVOKE } // end of enum MessageCompilerType //--------------------------------------------------------------- // 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 either a valid host name or IP address in dotted * notation. */ public static final String BIND_HOST_KEY = "bindHost"; /** * 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"; /** * eBus client task queue capacity {@value} property is used * to specify task queue maximum size for all eBus clients. *

* Default value is {@link #DEFAULT_TASK_QUEUE_CAPACITY}. *

*/ public static final String TASK_QUEUE_CAPACITY_KEY = "taskQueueCapacity"; /** * 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"; /** * Run queue capacity {@value} property is used to specify * run queue maximum size. Only used for non-blocking thread * types. *

* Default value is {@link #DEFAULT_RUN_QUEUE_CAPACITY}. *

*/ public static final String RUN_QUEUE_CAPACITY_KEY = "runQueueCapacity"; /** * 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"; /** * Reliable UDP re-transmits an application message after * waiting this long for an application message * acknowledgement receipt. */ public static final String RETRANSMIT_DELAY_KEY = "retransmitDelay"; /** * Reliable UDP re-transmits an application message this * many times before declaring the connection lost. This does * not include the initial message transmit. */ public static final String RETRANSMIT_LIMIT_KEY = "retransmitLimit"; /** * {@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"; /** * {@value} key is used to define {@link MessageCompilerType} * used to generate eBus message {@code DataType}s at * run time. */ private static final String COMPILER_KEY = "messageCompiler"; // // 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; /** * Default eBus message compiler type is * {@code MessageCompilerType.JAVA_ASSIST}. */ public static final MessageCompilerType DEFAULT_MESSAGE_COMPILER = MessageCompilerType.JAVA_ASSIST; /** * Default eBus client task queue capacity is {@value}. */ public static final int DEFAULT_TASK_QUEUE_CAPACITY = 1_024; /** * Default run queue capacity is {@value}. This value is * used only for non-blocking thread types and ignored for * blocking queues. */ public static final int DEFAULT_RUN_QUEUE_CAPACITY = 64; private static final String INVALID_ADDRESS = "is not a valid address"; private static final String INVALID_PORT = "is not a valid port"; 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"; private static final String INVALID_SERVICE = "invalid service configuration"; //----------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(EConfigure.class.getName()); //----------------------------------------------------------- // Locals. // /** * eBus Message compiler. */ private final MessageCompilerType mMessageCompiler; /** * 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 messageCompiler eBus message compiler. * @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 MessageCompilerType messageCompiler, final Map services, final Map conns, final Map mcastconns, final Map dispatchers) { mMessageCompiler = messageCompiler; 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 eBus message compiler type. * @return eBus message compiler type. */ public MessageCompilerType messageCompiler() { return (mMessageCompiler); } // end of messageCompiler() /** * 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 messageCompiler eBus message and field compiler. * @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 MessageCompilerType messageCompiler, final Map services, final Map connections, final Map mcastConnections, final Map dispatchers) { return (new EConfigure(messageCompiler, services, connections, mcastConnections, dispatchers)); } // end of create(...) /** * 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) { final MessageCompilerType messageCompiler = (config.hasPath(COMPILER_KEY) ? config.getEnum(MessageCompilerType.class, COMPILER_KEY) : DEFAULT_MESSAGE_COMPILER); return (new EConfigure(messageCompiler, 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) .address(loadAddress(HOST_KEY, PORT_KEY, false, true, config)) .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 (connType == ConnectionType.RELIABLE_UDP || connType == ConnectionType.SECURE_RELIABLE_UDP) { builder.retransmitDelay( config.getDuration(RETRANSMIT_DELAY_KEY)) .retransmitLimit( config.getInt(RETRANSMIT_LIMIT_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(HOST_KEY, PORT_KEY, true, true, config)) .bindAddress(loadAddress(BIND_HOST_KEY, BIND_PORT_KEY, false, false, config)) .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 (connType == ConnectionType.RELIABLE_UDP || connType == ConnectionType.SECURE_RELIABLE_UDP) { builder.retransmitDelay( config.getDuration(RETRANSMIT_DELAY_KEY)) .retransmitLimit( config.getInt(RETRANSMIT_LIMIT_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)) .bindHost(config.hasPath(BIND_HOST_KEY) ? parseAddress(BIND_HOST_KEY, config) : null) .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) /** * Returns socket address containing host and port retrieved * from configuration at the speicified host and port keys. * @param hostKey host address key. * @param portKey address key. * @param hostRequired if {@code true} then {@code hostKey} * must be in configuration. * @param portRequired if {@code true} then {@code portKey} * must be in configuration. * @param config retrieve host and port from this * configuration. * @return retrieved socket address. * @throws ConfigException * if either a required key is missing from {@code config} or * retrieved values are invalid. */ private static InetSocketAddress loadAddress(final String hostKey, final String portKey, final boolean hostRequired, final boolean portRequired, final Config config) { String host = null; int port = 0; final InetSocketAddress retval; // Is the host provided? if (config.hasPath(hostKey)) { // Yes, retrieve the value. host = config.getString(hostKey); } // No the host is not provided. Is the host required? else if (hostRequired) { throw (new ConfigException.Missing(hostKey)); } // Is the address provided? if (config.hasPath(portKey)) { // Yes, retrieve the value. port = config.getInt(portKey); } // No the address is not provided. Is the address required? else if (portRequired) { throw (new ConfigException.Missing(portKey)); } try { if (Strings.isNullOrEmpty(host)) { retval = new InetSocketAddress(port); } else { retval = new InetSocketAddress( InetAddress.getByName(host), port); } } catch (UnknownHostException hostex) { throw ( new ConfigException.BadValue( hostKey, "\"" + host + "\" " + INVALID_ADDRESS, hostex)); } catch (IllegalArgumentException argex) { throw ( new ConfigException.BadValue( portKey, port + " " + INVALID_PORT, argex)); } return (retval); } // end of loadAddress(String,String,boolean,boolean,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; /** * Re-transmit application message over reliable UDP * after waiting this long for an application message * receipt. *

* This configuration only applies to a reliable UDP * connection. *

*/ protected final Duration mRetransmitDelay; /** * Re-transmit application message at most this many * times before declaring reliable UDP connection lost. * Does not apply to initial transmit. *

* This configuration only applies to a reliable UDP * connection. *

*/ protected final int mRetransmitLimit; //----------------------------------------------------------- // 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; this.mRetransmitDelay = builder.mRetransmitDelay; this.mRetransmitLimit = builder.mRetransmitLimit; } // 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() /** * Returns reliable UDP application message re-transmit * delay. Applies only to * {@link ConnectionType#RELIABLE_UDP}; ignored by all * other connection types. * @return re-transmit delay. */ public final Duration retransmitDelay() { return (mRetransmitDelay); } // end of retransmitDelay() /** * Returns reliable UDP application message re-transmit * limit. Applies only to * {@link ConnectionType#RELIABLE_UDP}; ignored by all * other connection types. * @return re-transmit limit. */ public final int retransmitLimit() { return (mRetransmitLimit); } // end of retransmitLimit() // // 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
PropertyRequired?TypeDefaultDescription
{@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 host}No{@code String}Wildcard addressServer socket host name or dotted IP address.
{@code port}Yes{@code int} > zero and < 65,536NA * 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 host and port. */ private final InetSocketAddress mAddress; /** * 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.mAddress = builder.mAddress; 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) { final InetSocketAddressComparator comparator = new InetSocketAddressComparator(); return ( comparator.compare(mAddress, service.mAddress)); } // 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 address 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 address 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 && Objects.equals(mAddress, svc.mAddress)); } return (retcode); } // end of equals(Object) /** * Returns the service address. * @return the service address. */ @Override public int hashCode() { return (Objects.hash(mConnectionType, mAddress)); } // 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(mAddress) .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 host and port address. * @return the service host and port address. */ public InetSocketAddress address() { return (mAddress); } // end of address() /** * 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
PropertyRequired?TypeDefaultDescription
{@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 bindHost}No{@code String}Wildcard address * Bind connection local host to this value. If set to * {@code null} then local address is bound to the * wildcard address. *
bindPortNo{@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 : 54321
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 host and port. */ private final InetSocketAddress mBindAddress; /** * 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.mBindAddress = builder.mBindAddress; 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(mBindAddress) .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 host and port to which * local address is bound. * @return the connection bind address. */ public InetSocketAddress bindAddress() { return (mBindAddress); } // end of bindHost() /** * 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
PropertyRequired?TypeDefaultDescription
{@code taskQueueCapacity}Noint > zero{@link #DEFAULT_TASK_QUEUE_CAPACITY} * eBus client task queue capacity. Value should be * an exact 2 power. If not, queue size is set to next * 2 power > given capacity. *
{@code runQueueType}No{@link ThreadType}{@link #DEFAULT_THREAD_TYPE} * Dispatcher thread is implemented as this type. *
{@code runQueueCapacity}Noint > zero{@link #DEFAULT_TASK_QUEUE_CAPACITY} * Run queue capacity. Value should be an exact 2 * power. If not, queue size is set to next 2 power * > given capacity. *
{@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}NoArray of {@link ThreadAffinityConfigure}Empty list * Optional OpenHFT thread affinity configurations. *
*

* Example dispatcher configuration. *

*
name : d1
taskQueueCapacity : 256
runQueueType : "spin+park"
runQueueCapacity : 2048
spinLimit : 2500000
parkTime : 500ns
isDefault : true
priority : 5
quantum : 10000ns
numberThreads : 4
*

Programmatic Dispatcher Configuration

* Dispatchers may be created at run time using * {@link net.sf.eBus.config.EConfigure.DispatcherBuilder}. * The following example creates a spinning, single thread * {@code Dispatcher} for the singleton * {@code MarketDataHandler} instance. *

* * Note: This example only works if the * dispatcher is created before * {@code MarketDataHandler} instantiated. If not, the * {@code MarketDataHandler} instance will be assigned to * the default {@code Dispatcher}. * *

*
import net.sf.eBus.client.EFeed;
import net.sf.eBus.config.EConfigure.Dispatcher;
import net.sf.eBus.config.EConfigure.DispatcherBuilder;
import net.sf.eBus.config.EConfigure.DispatcherType;
import net.sf.eBus.config.EConfigure.ThreadAffinityConfigure;
import net.sf.eBus.config.ThreadType;

public static void main(final String[] args) {
    final Class[] eclients = new Class[] { MarketDataHandler.class };
    final ThreadAffinityConfigure threadAffinity =
        (ThreadAffinityConfigure.builder()).affinityType(ThreadsAffinityConfigure.AffinityType.CPU_ID)
                                           .cpuId(7)
                                           .bind(true)
                                           .wholeCore(true)
                                           .build();
    final ThreadAffinityConfigure[] threadAffinities =
        new ThreadAffinityConfigure[] { threadAffinity };
    final DispatcherBuilder builder = EConfigure.dispatcherBuilder();
    final Dispatcher dispatcher = builder.name("MyDispatcher")
                                         .taskQueueCapacity(256)
                                         .dispatcherType(DispatcherType.EBUS)
                                         .threadType(ThreadType.SPINNING)
                                         .runQueueCapacity(2048)
                                         .numberThreads(1)
                                         .isDefault(false)
                                         .classes(eclients)
                                         .threadAffinity(threadAffinities)
                                         .build();

    EFeed.createDispatcher(dispatcher);
}
*

* See * {@link net.sf.eBus.config.ThreadAffinityConfigure.Builder} * for explanation on how to build a * {@link net.sf.eBus.config.ThreadAffinityConfigure} * instances. *

*

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 eBus client task queue capacity. */ private final int mTaskQueueCapacity; /** * Specifies how the dispatcher acquires the next client * from the run queue: blocking, spinning, spin+park, or * spin+yield. */ private final ThreadType mRunQueueType; /** * Specifies run queue capacity. This value is only used * for non-blocking queues. Ignored for blocking queue. */ private final int mQueueCapacity; /** * 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.mTaskQueueCapacity = builder.mTaskQueueCapacity; this.mRunQueueType = builder.mRunQueueType; this.mQueueCapacity = builder.mQueueCapacity; 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 eBus client task queue capacity. * @return eBus client task queue capacity. */ public int taskQueueCapacity() { return (mTaskQueueCapacity); } // end of taskQueueCapacity() /** * 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 run queue maximum capacity. Used only for * non-blocking run queue types and ignored for blocking * run queue. * @return non-blocking run queue maximum capacity. */ public int runQueueCapacity() { return (mQueueCapacity); } // end of runQueueCapacity() /** * 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
PropertyRequired?TypeDefaultDescription
{@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} ≥ zeroNA * 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
PropertyRequired?TypeDefaultDescription
{@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} ≥ zeroNA * 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} ≥ zeroZero * 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
PropertyRequired?TypeDefaultDescription
{@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}YesArray 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}NoArray 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 mBindHost; /** * 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; mBindHost = builder.mBindHost; 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(mBindHost) .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 (mBindHost); } // 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
PropertyRequired?TypeDefaultDescription
{@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; /** * Re-transmit application message over reliable UDP * after waiting this long for an application message * receipt. *

* This configuration only applies to a reliable UDP * connection. *

*/ protected Duration mRetransmitDelay; /** * Re-transmit application message at most this many * times before declaring reliable UDP connection lost. * Does not apply to initial transmit. *

* This configuration only applies to a reliable UDP * connection. *

*/ protected int mRetransmitLimit; //----------------------------------------------------------- // 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; mRetransmitDelay = null; mRetransmitLimit = 0; 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) /** * Sets reliable UDP application message re-transmit * delay. * @param delay application message re-transmit delay. * @return {@code this} server/connection builder. * @throws ConfigException * if {@code delay} is either {@code null} or a negative * value. */ public final T retransmitDelay(final Duration delay) { if (delay == null) { throw ( new ConfigException.BadValue( RETRANSMIT_DELAY_KEY, "re-transmit delay is null")); } if (delay.isNegative() || delay.isZero()) { throw ( new ConfigException.BadValue( RETRANSMIT_DELAY_KEY, "re-transmit delay <= 0")); } mRetransmitDelay = delay; return ((T) this); } // end of retransmitDelay(Duration) /** * Sets reliable UDP application message re-transmit * limit. This limit does not include the * initial message transmit. * @param limit maximum number of times an application * message may be sent before declaring a reliable UDP * connection lost. * @return {@code this} server/connection builder. * @throws ConfigException * if {@code limit} is ≤ zero. */ public final T retransmitLimit(final int limit) { if (limit <= 0) { throw ( new ConfigException.BadValue( RETRANSMIT_LIMIT_KEY, "re-transmit limit <= 0")); } mRetransmitLimit = limit; return ((T) this); } // end of retransmitLimit(int) /** * 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(); mRetransmitDelay = config.retransmitDelay(); mRetransmitLimit = config.retransmitLimit(); 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") .requireTrue((mConnectionType != ConnectionType.RELIABLE_UDP || mRetransmitDelay != null), RETRANSMIT_DELAY_KEY, "re-transmit delay not set") .requireTrue((mConnectionType != ConnectionType.RELIABLE_UDP || mRetransmitLimit > 0), RETRANSMIT_LIMIT_KEY, "re-transmit limit 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 address 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")
                          .address(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 InetSocketAddress mAddress; private AddressFilter mAddressFilter; private String mServiceSelector; private String mConnSelector; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new server configuration builder. */ private ServerBuilder() { mAddress = new InetSocketAddress(ANY_PORT); 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); mAddress = config.address(); mAddressFilter = config.addressFilter(); mServiceSelector = config.serviceSelector(); mConnSelector = config.connectionSelector(); } return (this); } // end of configuration(EConfigure.Service) /** * Set service TCP to given port and local wildcard * address. * @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 + ")")); } return (address(new InetSocketAddress(port))); } // end of address(int) /** * Sets the service TCP host and address. * @param address service TCP host and address. * @return {@code this} service builder. * @throws ConfigException * if {@code address} is {@code null}. */ public ServerBuilder address(final InetSocketAddress address) { if (address == null) { throw ( new ConfigException.BadValue( BIND_HOST_KEY, "address is null")); } mAddress = address; return (this); } // end of address(InetSocketAddress) /** * 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 address 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, 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) .requireNotNull(mAddress, HOST_KEY)); } // 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 address: {@code ERemoteApp.ANY_PORT} and * wildcard address. *
  • *
  • * 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 InetSocketAddress mBindAddress; private String mSelector; private boolean mReconnectFlag; private Duration mReconnectTime; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new eBus connection configuration builder. */ private ConnectionBuilder() { mAddress = null; mBindAddress = null; 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(); mBindAddress = config.bindAddress(); 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 port and the * wildcard address. * @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 + ")")); } return (bindAddress(new InetSocketAddress(port))); } // end of bindPort(int) /** * Bind the connection local address to given address. * If {@code address} is set to {@code null}, then * connection local address is set to an * automatically assigned socket address and port. * @param address connection local bind address. * @return {@code this} connection builder. */ public ConnectionBuilder bindAddress(@Nullable final InetSocketAddress address) { mBindAddress = address; return (this); } // end of bindAddresst(InetSocketAddress) /** * 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, 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: *

*
    *
  • * client task queue capacity: * {@link EConfigure#DEFAULT_TASK_QUEUE_CAPACITY}. *
  • *
  • * run queue capacity: * {@link EConfigure#DEFAULT_RUN_QUEUE_CAPACITY}. *
  • *
  • * 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}. *
  • *
*

* A {@code Dispatcher} can be created programmatically using * a {@code DispatchBuilder} as follows: *

*

* * Note: This example only works if the * dispatcher is created before * {@code MarketDataHandler} instantiated. If not, the * {@code MarketDataHandler} instance will be assigned to * the default {@code Dispatcher}. * *

*
import net.sf.eBus.client.EFeed;
import net.sf.eBus.config.EConfigure.Dispatcher;
import net.sf.eBus.config.EConfigure.DispatcherBuilder;
import net.sf.eBus.config.EConfigure.DispatcherType;
import net.sf.eBus.config.EConfigure.ThreadAffinityConfigure;
import net.sf.eBus.config.ThreadType;

public static void main(final String[] args) {
    final Class[] eclients = new Class[] { MarketDataHandler.class };
    final ThreadAffinityConfigure threadAffinity =
        (ThreadAffinityConfigure.builder()).affinityType(ThreadsAffinityConfigure.AffinityType.CPU_ID)
                                           .cpuId(7)
                                           .bind(true)
                                           .wholeCore(true)
                                           .build();
    final ThreadAffinityConfigure[] threadAffinities =
        new ThreadAffinityConfigure[] { threadAffinity };
    final DispatcherBuilder builder = EConfigure.dispatcherBuilder();
    final Dispatcher dispatcher = builder.name("MyDispatcher")
                                         .dispatcherType(DispatcherType.EBUS)
                                         .taskQueueCapacity(256)
                                         .threadType(ThreadType.SPINNING)
                                         .runQueueCapacity(32)
                                         .numberThreads(1)
                                         .isDefault(false)
                                         .classes(eclients)
                                         .threadAffinity(threadAffinities)
                                         .build();

    EFeed.createDispatcher(dispatcher);
}
*

* See * {@link net.sf.eBus.config.ThreadAffinityConfigure.Builder} * for explanation on how to build a * {@link net.sf.eBus.config.ThreadAffinityConfigure} * instances. *

* * @author Charles W. Rapp */ public static final class DispatcherBuilder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // private String mName; private DispatcherType mType; private int mTaskQueueCapacity; private ThreadType mRunQueueType; private int mQueueCapacity; 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; mTaskQueueCapacity = DEFAULT_TASK_QUEUE_CAPACITY; mRunQueueType = null; mQueueCapacity = DEFAULT_RUN_QUEUE_CAPACITY; 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 eBus client task queue capacity. This capacity * is applied to each eBus client. The capacity should be * a 2 power value. If not, run queue capacity is set to * the next 2 power value > given capacity. * @param capacity task queue maximum capacity. * @return {@code this} dispatcher builder. * @throws ConfigException * if {@code capacity} is ≤ zero. */ public DispatcherBuilder taskQueueCapacity(final int capacity) { if (capacity <= 0) { throw ( new ConfigException.BadValue( TASK_QUEUE_CAPACITY_KEY, "capacity <= zero")); } mTaskQueueCapacity = capacity; return (this); } // end of taskQueueCapacity(int) /** * 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 run queue capacity. This setting is used only for * non-blocking Dispatcher thread type. The capacity * should be a 2 power value. If not, run queue capacity * is set to the next 2 power value > given capacity. * @param capacity run queue maximum capacity. * @return {@code this} dispatcher builder. * @throws ConfigException * if {@code capacity} is ≤ zero. */ public DispatcherBuilder runQueueCapacity(final int capacity) { if (capacity <= 0) { throw ( new ConfigException.BadValue( RUN_QUEUE_CAPACITY_KEY, "capacity <= zero")); } mQueueCapacity = capacity; return (this); } // end of runQueueCapacity(int) /** * 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) { if (affinities != null) { mAffinity = affinities.toArray( ThreadAffinityConfigure[]::new); } return (this); } // 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, 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, 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 mBindHost; 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 host bind socket local side to this address. * May be {@code null}. * @return {@code this} multicast connection builder. */ public MulticastBuilder bindHost(final InetAddress host) { mBindHost = host; return (this); } // end of bindHost(InetAddress) /** * Bind the multicast connection local port to this * value. If {@code port} is set to * {@link ENetConfigure#ANY_PORT}, then the connection * local address 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_HOST_KEY, "invalid bind port (" + port + ")")); } mBindPort = port; return (this); } // end of bindHost(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, 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, 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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy