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

net.sf.eBus.client.EServer Maven / Gradle / Ivy

The newest version!
//
// Copyright 2001 - 2008, 2011, 2015, 2016, 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.client;

import com.google.common.base.Strings;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectableChannel;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.config.AddressFilter;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.config.EConfigure.ConnectionType;
import static net.sf.eBus.config.EConfigure.ConnectionType.TCP;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.util.MultiKey2;
import net.sf.eBus.util.logging.StatusReport;
import net.sf.eBus.util.logging.StatusReporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Accepts new connections to this eBus application. If an
 * address filter is set, verifies that the accepted connection
 * passes the filter. Servers are distinguished by
 * {@link ConnectionType connection type} and address. Note
 * that {@link ConnectionType#TCP plain text} and
 * {@link ConnectionType#SECURE_TCP secure} TCP connections are
 * considered the same for these purposes.
 * 

* Applications wishing to be notified when {@code EServer} * accepts a new remote eBus application connection need to * implement the {@link ESubscriber} interface and subscribe * for the message key * {@link ServerMessage#MESSAGE_KEY net.sf.eBus.client.ServerMessage:/eBus}. * {@link ServerMessage} contains the remote address of the * newly accepted TCP connection. Note: this is * a local-only feed and cannot be remote accessed. *

* * @author Charles Rapp */ public abstract class EServer implements EPublisher, StatusReporter { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * TCP ports must be >= to 1. */ public static final int MIN_PORT = 1; /** * TCP ports must be <= 65,535. */ public static final int MAX_PORT = 65535; //----------------------------------------------------------- // Statics. // /** * eBus may have at most two server sockets open: one for * accepting binary connections and one for XML connections. */ private static final ConcurrentMap, EServer> sServers = new ConcurrentHashMap<>(); private static final DateTimeFormatter sFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd @ HH:mm:ss.SSS"); /** * The logging subsystem interface. */ private static final Logger sLogger = LoggerFactory.getLogger(EServer.class); //----------------------------------------------------------- // Locals. // /** * Server is for this eBus connection type. */ protected final ConnectionType mType; /** * The service is open on this TCP/IP address. */ protected final InetSocketAddress mAddress; /** * Contains the configuration information used for accepted * socket connections. */ protected final Service mConfiguration; /** * Service name based on type and address. */ protected final String mName; /** * Accept connections only if there are in this filter. * If null, then accept all connections. */ private final AddressFilter mPositiveFilter; /** * Set to true when the service has been opened. */ private boolean mIsOpen; /** * The instance creation timestamp. */ private final LocalDateTime mCreated; /** * Tally up the number of connections accepted during the * current status report period. */ private int mAcceptCount; /** * Total number of accepted connections since server * creation. */ private int mTotalAcceptCount; /** * Publish new connection notifications on this feed. */ private IEPublishFeed mNewConnectionFeed; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a server socket that will listen on the specified address and create {@link ERemoteApp} objects. The parameters * are used to configure the accept remote connections. * @param config eBus service configuration. */ protected EServer(final Service config) { mType = config.connectionType(); mAddress = config.address(); mName = mType + "-server-" + mAddress; mPositiveFilter = config.addressFilter(); mConfiguration = config; mIsOpen = false; mCreated = LocalDateTime.now(); mAcceptCount = 0; } // end of EServer(Service) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Opens the underlying server socket for the given address. * @throws IOException * if an I/O failure occurs while creating the server socket. */ protected abstract void doOpen() throws IOException; /** * Closes the underlying server socket. */ protected abstract void doClose(); /** * Cleans up the accepted channel after a connect attempt * failed. * @param text text explaining why the connect attempt failed. * @param channel the accepted channel. */ protected abstract void doAcceptFailed(String text, SelectableChannel channel); // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // StatusReporter Interface Implementation // /** * Adds the eBus server connection status to the status * report. * @param report the logged status report. */ @Override public void reportStatus(final PrintWriter report) { int acceptCount = mAcceptCount; // Reset the report period accept count. mAcceptCount = 0; report.format("The eBus service is open on %s.%n", mAddress) .format(" created on %s%n", sFormatter.format(mCreated)) .format(" accepted %,d %s.%n", acceptCount, (acceptCount == 1 ? "connection" : "connections")) .format("total accepted %,d %s.%n", mTotalAcceptCount, (mTotalAcceptCount == 1 ? "connection" : "connections")); } // end of reportStatus(PrintWriter) // // end of StatusReporter Interface Implementation //----------------------------------------------------------- //----------------------------------------------------------- // EObject Interface Implementation. // /** * Returns eBus object name. May not be unique. * @return eBus object name. */ @Override public String name() { return (mName); } // end of name() // // end of EObject Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // EPublisher Interface Implementation. // /** * Updates the feed state. This method does nothing since the * feed state is stored in the publish feed. * @param pubState the new publisher feed state * @param feed the feed state applies to this feed. */ @Override public void publishStatus(final EFeedState pubState, final IEPublishFeed feed) {} // end of publishStatus(EFeedState, IEPublishFeed) // // end of EPublisher Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Get methods. // /** * Returns the eBus connection type for this server. * @return eBus connection type. */ public ConnectionType connectionType() { return (mType); } // end of connectionType() /** * Returns host on which this service is accepting * connections. * @return address on which this service is accepting * connections. */ public @Nullable InetAddress host() { return (mAddress.getAddress()); } // end of host() /** * Returns port on which this service is accepting * connections. * @return port on which this service is accepting * connections. */ public int port() { return (mAddress.getPort()); } // end of address() /** * Returns address and port on which this service is * accepting connections. * @return server host and port. */ public InetSocketAddress address() { return (mAddress); } // end of address() /** * Returns eBus service configuration. * @return eBus service configuration. */ public Service configuration() { return (mConfiguration); } // end of configuration() /** * Returns {@code true} if this server is open and * {@code false} otherwise. * @return {@code true} if this server is open. */ public boolean isOpen() { return (mIsOpen); } // end of isOpen() /** * Returns {@code true} if the singleton eBus service exists * and is open; otherwise, returns {@code false}. * @param type eBus connection type. * @param address check if this service address is open. * @return {@code true} if this eBus application has an open * service. */ public static boolean isServiceOpen(final ConnectionType type, final SocketAddress address) { final MultiKey2 sKey = new MultiKey2<>(normalize(type), address); final EServer server = sServers.get(sKey); return (server != null && server.mIsOpen); } // end of isServiceOpen(ConnectionType, SocketAddress) /** * Returns the number of existing eBus services for the * given connection type. * @param type eBus connection type. * @return number of existing eBus services for the given * connection type. */ public static int serviceCount(final ConnectionType type) { final ConnectionType normalized = normalize(type); int retval = 0; return (sServers.keySet() .stream() .filter(k -> (normalized == k.key(0))) .map(item -> 1) .reduce(retval, Integer::sum)); } // end of serviceCount(ConnectionType) /** * Returns the number of existing eBus services. * @return number of existing eBus services. */ public static int serviceCount() { return (sServers.size()); } // end of serviceCount() /** * Returns a copy of the existing eBus service ports. * @return existing eBus services. */ public static Collection> services() { return (new ArrayList<>(sServers.keySet())); } // end of services() /** * Returns the eBus server instance associated with the given * connection type and address. May return {@code null} if * there is no eBus eBus server for {@code type} and * {@code address}. * @param type eBus connection type. * @param address eBus server address. * @return eBus server instance. */ @Nullable public static EServer server(final ConnectionType type, final SocketAddress address) { final MultiKey2 sKey = new MultiKey2<>(normalize(type), address); return (sServers.get(sKey)); } // end of server(ConnectionType, SocketAddress) // // end of Get methods. //----------------------------------------------------------- /** * Opens a service socket on the given port and accepting * connections of the given type and from the specified * hosts and ports. The accepted connections * are configured as per the given service configuration. * Returns the opened {@code EServer} instance. *

* If * {@link net.sf.eBus.config.EConfigure.Service#addressFilter() address filter} * contains an entry with a socket address but a address set * to zero, that means the client connection may be bound to * any address. If the address is > zero, then the client * connection must be bound to the specific address. If the * address filter is {@code null}, then all connections are * accepted. *

*

* Negative filters are not supported. A negative filter * would accept all connections except those listed. *

* @param config eBus service configuration. * @return the opened server instance. * @throws IllegalArgumentException if any of the given * parameters is invalid. * @throws IllegalStateException * if service address is already open. */ public static EServer openServer(final Service config) { Objects.requireNonNull(config, "config is null"); final ConnectionType cType = normalize(config.connectionType()); final InetSocketAddress address = config.address(); final MultiKey2 sKey = new MultiKey2<>(cType, address); if (sServers.containsKey(sKey)) { throw ( new IllegalStateException( "service already open")); } final EServer retval = createServer(config); sServers.put(sKey, retval); (StatusReport.getsInstance()).register(retval); sLogger.debug("Opening {} server on {}:\n{}", cType, address, config); if (!retval.open()) { retval.close(); throw ( new IllegalStateException( "service failed to open")); } return (retval); } // end of openServer(Service) /** * Closes the specified eBus service if open. * @param cType eBus connection type. * @param address close the service on this service address. */ public static void closeServer(final ConnectionType cType, final SocketAddress address) { final MultiKey2 sKey = new MultiKey2<>(normalize(cType), address); if (sServers.containsKey(sKey)) { final EServer server = sServers.remove(sKey); (StatusReport.getsInstance()).deregister(server); server.close(); } } // end of closeServer(ConnectionType, SocketAddress) /** * Closes are currently open eBus servers. */ public static void closeAllServers() { final StatusReport report = StatusReport.getsInstance(); sServers.values() .stream() .map( s -> { report.deregister(s); return (s); }) .forEachOrdered(s -> s.close()); sServers.clear(); } // end of closeAllServers() /** * Creates and opens an eBus service for this application as * per the {@link net.sf.eBus.config.EConfigure eBus configuration}. * @param config the eBus configuration. * @throws IOException * if the configured eBus service failed to open. */ public static void configure(final EConfigure config) throws IOException { (config.services()).values() .forEach(EServer::openServer); } // end of configure(EConfigure) /** * Accepts the connection if the far-end address passes the * address filter. If it fails, the connect attempt is * rejected. If it passes, then a new {@code ERemoteApp} * connection is opened. * @param iAddress far-end address. * @param channel accepted channel. */ protected void acceptConnection(final InetSocketAddress iAddress, final SelectableChannel channel) { // Accept the client connection if: // 1. There is no filter in place. // 2. The filter contains the client's inetAddress and // address. // 3. The filter contains the client's inetAddress and // accepts any address. if (mPositiveFilter != null && !mPositiveFilter.passes(iAddress)) { sLogger.info( "Accepted unknown client connection {}; disconnecting.", iAddress); doAcceptFailed("unknown client connection", channel); } else { sLogger.info("Accepted client from {}.", iAddress); ++mAcceptCount; ++mTotalAcceptCount; ERemoteApp.openConnection(this, mType, iAddress, channel, mConfiguration); // Tell the listeners about this new remote // application connection. if (mNewConnectionFeed.isFeedUp()) { mNewConnectionFeed.publish( (ServerMessage.builder()).connectionType(mType) .remoteAddress(iAddress) .serverAddress(mAddress) .build()); } } } // end of acceptConnection(...) /** * Server socket is unexpectedly closed. Marks the server as * closed and close the server feed. * @param jex exception behind the server close. */ protected void serverClosed(final Throwable jex) { final String message = jex.getMessage(); mIsOpen = false; mNewConnectionFeed.close(); sLogger.warn("Service on {} unexpectedly closed, {}.", mAddress, (Strings.isNullOrEmpty(message) ? "no reason given." : message), jex); } // end of serverClosed(Throwable) /** * Performs the actual work of instantiating the eBus server * and opening it on the specified port. Returns {@code true} * if the eBus service is successfully opened. * @return {@code true} if the eBus service is open. */ private boolean open() { // Open the service only if this service is closed. if (!mIsOpen) { sLogger.debug("Opening service on address {}.", mAddress); try { // No. Establish the service. doOpen(); mIsOpen = true; // Tell the world that this service is open. sLogger.info("Service open on {}.", mAddress); // Open the new connection notification feed. mNewConnectionFeed = openFeed(); } catch (IOException ioex) { final String message = ioex.getMessage(); sLogger.warn( "Failed to open eBus service on {}, {}.", mAddress, ((message == null || message.length() == 0) ? "no reason given." : message), ioex); } } return (mIsOpen); } // end of open() /** * Returns a publish feed for the "new connection" * notification. This feed is both advertised and with an * up feed state. * @return "new connection" publish feed. */ private IEPublishFeed openFeed() { final EPublishFeed.Builder builder = EPublishFeed.builder(); final IEPublishFeed retval = builder.target(this) .messageKey(ServerMessage.MESSAGE_KEY) .scope(FeedScope.LOCAL_ONLY) .build(); retval.advertise(); retval.updateFeedState(EFeedState.UP); return (retval); } // end of openFeed() /** * Performs the actual work of closing the open eBus service. */ private void close() { // Close the service only if this service is open. if (mIsOpen) { sLogger.debug("Closing service on {}.", mAddress); mIsOpen = false; doClose(); // Close the new connection feed as well. mNewConnectionFeed.close(); mNewConnectionFeed = null; sLogger.info("Service closed on {}.", mAddress); } } // end of close() /** * If {@code type} is {@link ConnectionType#SECURE_TCP}, then * returns {@link ConnectionType#TCP}; otherwise returns * {@code type}. * @param type normalizes this connection type. * @return normalized connection type. */ private static ConnectionType normalize(final ConnectionType type) { return (type == ConnectionType.SECURE_TCP ? ConnectionType.TCP : type); } // end of normalize(ConnectionType) /** * Returns the {@code EServer} instance of the appropriate * subclass based on the connection type. * @param config server configuration. * @return eBus server instance. */ private static EServer createServer(final Service config) { final EServer retval; switch (config.connectionType()) { case TCP: case SECURE_TCP: retval = new ETCPServer(config); break; case UDP: case SECURE_UDP: retval = new EUDPServer(config); break; default: retval = new EReliableUDPServer(config); } return (retval); } // end of createServer(Service) } // end of class EServer




© 2015 - 2024 Weber Informatics LLC | Privacy Policy