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

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

//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2001 - 2008, 2011, 2015, 2016. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.client;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.config.AddressFilter;
import net.sf.eBus.config.EConfigure;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.net.AsyncServerSocket;
import net.sf.eBus.net.ServerSocketListener;
import net.sf.eBus.util.logging.StatusReport;
import net.sf.eBus.util.logging.StatusReporter;

/**
 * Accepts new connections to this eBus application. If an
 * address filter is set, verifies that the accepted connection
 * passes the filter.
 * 

* Only one {@code EServer} instance may be open at a * time. *

* Applications wanting 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 final class EServer implements ServerSocketListener, 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; /** * Newly accepted eBus connections are reported using * {@link ServerMessage} and the subject * {@link AbstractEBusMessage#EBUS_SUBJECT "/eBus"}. */ public static final EMessageKey NEW_CONNECTION_KEY = new EMessageKey(ServerMessage.class, AbstractEBusMessage.EBUS_SUBJECT); //----------------------------------------------------------- // Statics. // /** * eBus may have at most two server sockets open: one for * accepting binary connections and one for XML connections. */ private static final ConcurrentMap sServers = new ConcurrentHashMap<>(); /** * The logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(EServer.class.getName()); //----------------------------------------------------------- // Locals. // /** * The service is open on this TCP/IP port. */ private int mPort; /** * Accept connections only if there are in this filter. * If null, then accept all connections. */ private final AddressFilter mPositiveFilter; /** * Contains the configuration information used for accepted * socket connections. */ private final EConfigure.Service mConfiguration; /** * Set to true when the service has been opened. */ private boolean mIsOpen; /** * The actual service socket. */ private AsyncServerSocket mAsyncServer; /** * The instance creation timestamp. */ private final Date mCreated; /** * Tally up the number of connections accepted during the * current status report period. */ private int mAcceptCount; /** * Publish new connection notifications on this feed. */ private IEPublishFeed mNewConnectionFeed; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a server socket that will listen on the specified * port and create {@link ERemoteApp} objects. The parameters * are used to configure the accept remote connections. * @param config eBus service configuration. */ private EServer(final EConfigure.Service config) { mPort = -1; mPositiveFilter = config.addressFilter(); mConfiguration = config; mIsOpen = false; mAsyncServer = null; mCreated = new Date(); mAcceptCount = 0; } // end of EServer(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // ServerSocketListener Interface Implementation // /** * Create a {@link ERemoteApp} to handle this new * connection if the connection passes the positive filter. * If not, the accepted client connection is immediately * closed. * @param socket a {@code Socket} connection * @param aserver an * {@link net.sf.eBus.net.AsyncServerSocket} */ @Override public void handleAccept(final SocketChannel socket, final AsyncServerSocket aserver) { final InetSocketAddress iAddress = (InetSocketAddress) (socket.socket()).getRemoteSocketAddress(); ++mAcceptCount; // Accept the client connection if: // 1. There is no filter in place. // 2. The filter contains the client's inetAddress and port. // 3. The filter contains the client's inetAddress and // accepts any port. if (mPositiveFilter != null && !mPositiveFilter.passes(iAddress)) { sLogger.info( String.format( "Accepted unknown client connection %s; disconnecting.", iAddress)); try { socket.close(); } catch (IOException ioex) { // Ignore exception when closing. } } else { sLogger.info( String.format( "Accepted client from %s.", iAddress)); // Tell the listeners about this new remote // application connection. if (mNewConnectionFeed.isFeedUp()) { mNewConnectionFeed.publish( new ServerMessage(iAddress, mPort)); } ERemoteApp.openConnection(mPort, socket, mConfiguration); } return; } // end of handleAccept(SocketChannel, AsyncServerSocket) /** * The eBus service has unexpectedly closed. Re-open it. * @param jex an {@code Exception} value * @param aserver an * {@link net.sf.eBus.net.AsyncServerSocket} */ @Override public void handleClose(final Throwable jex, final AsyncServerSocket aserver) { final String message = jex.getMessage(); // Before doing anything else, drop the server socket // reference and close the notification feed. mAsyncServer = null; mIsOpen = false; mNewConnectionFeed.close(); sLogger.log( Level.WARNING, String.format( "Service on port %d unexpectedly closed, %s.", mPort, ((message == null || message.length() == 0) ? "no reason given." : message)), jex); return; } // end of handleClose(Throwable, AsyncServerSocket) // // ServerSocketListener Interface Implementation //----------------------------------------------------------- //----------------------------------------------------------- // 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; mAcceptCount = 0; report.format( "The eBus service is open on port %d.%n", mPort); report.format( " created on %1$tY-%1$tm-%1$td @ %1$tH:%1$tM:%1$tS.%1$tL%n", mCreated); report.format(" accepted %,d %s.%n", acceptCount, (acceptCount == 1 ? "connection" : "connections")); return; } // end of reportStatus(PrintWriter) // // end of StatusReporter 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 TCP port on which this service is accepting * connections. * @return the TCP port on which this service is accepting * connections. */ public int port() { return (mPort); } // end of port() /** * Returns the eBus service configuration. * @return eBus service configuration. */ public EConfigure.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 port check if this service port is open. * @return {@code true} if this eBus application has an open * service. */ public static boolean isServiceOpen(final int port) { final EServer server = sServers.get(port); return (server != null && server.mIsOpen); } // end of isServiceOpen(int) /** * Returns the number of currently open eBus services. * @return number of open eBus services. */ public static int serviceCount() { return (sServers.size()); } // end of serviceCount() /** * Returns a copy of the existing eBus service ports. * @return open eBus service ports. */ public static Collection services() { final Collection retval = new ArrayList<>(); retval.addAll(sServers.keySet()); return (retval); } // end of services() /** * Returns the eBus server instance associated with the given TCP serverPort. May return {@code null} if there is no eBus * server for {@code serverPort}. * @param port TCP service serverPort. * @return eBus server instance. */ public static EServer server(final int port) { return (sServers.get(port)); } // end of server(int) // // 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 EConfigure.Service#addressFilter() address filter} * contains an entry with a socket address but a port set to * zero, that means the client connection may be bound to any * port. If the port is > zero, then the client connection * must be bound to the specific port. 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. * @exception IllegalStateException * if service port is already open. */ public static EServer openServer(final EConfigure.Service config) { Objects.requireNonNull(config, "config is null"); final int port = config.port(); if (sServers.containsKey(port)) { throw ( new IllegalStateException( "service already open")); } final EServer retval = new EServer(config); sServers.put(port, retval); (StatusReport.getInstance()).register(retval); if (!retval.open(port)) { retval.close(); throw ( new IllegalStateException( "service failed to open")); } return (retval); } // end of openServer(Service) /** * Closes the specified service socket if open. * @param port close the service on this service port. */ public static void closeServer(final int port) { if (sServers.containsKey(port)) { final EServer server = sServers.remove(port); (StatusReport.getInstance()).deregister(server); server.close(); } return; } // end of closeServer(int) /** * Closes are currently open eBus servers. */ public static void closeAllServers() { final StatusReport report = StatusReport.getInstance(); sServers.values() .stream() .map((server) -> { report.deregister(server); return server; }) .forEachOrdered((server) -> { server.close(); }); sServers.clear(); return; } // end of closeAllServers() /** * Creates and opens an eBus service for this application as * per the {@link 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); return; } // end of configure(EConfigure) /** * 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. * @param port a TCP service port. * @return {@code true} if the eBus service is open. */ private boolean open(final int port) { // Open the service only if this service is // supposed to be open. if (mAsyncServer == null || !mAsyncServer.isOpen()) { if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "Opening service on port %d.", port)); } try { // No. Establish the service. if (mAsyncServer == null) { final net.sf.eBus.net.AsyncServerSocket.ServerBuilder builder = net.sf.eBus.net.AsyncServerSocket.builder(); mAsyncServer = builder.selector(mConfiguration.serviceSelector()) .listener(this) .build(); } mAsyncServer.open(port); mPort = port; mIsOpen = true; // Tell the world that this service is open. if (sLogger.isLoggable(Level.INFO)) { sLogger.info( String.format( "Service open on port %d.", port)); } // Open the new connection notification feed. // Open the new connection notification feed // This is a local client/local-only feed. mNewConnectionFeed = EPublishFeed.open(this, NEW_CONNECTION_KEY, FeedScope.LOCAL_ONLY); mNewConnectionFeed.advertise(); mNewConnectionFeed.updateFeedState( EFeedState.UP); } catch (IOException ioex) { final String message = ioex.getMessage(); sLogger.log( Level.WARNING, String.format( "Failed to open eBus service on port %d, %s.", port, ((message == null || message.length() == 0) ? "no reason given." : message)), ioex); } } return (mIsOpen); } // end of open(int, String) /** * Performs the actual work of closing the open eBus service. */ private void close() { final AsyncServerSocket asyncServer = mAsyncServer; // Close the service only if this service is open. if (mIsOpen && asyncServer != null && asyncServer.isOpen()) { if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "Closing service on port %d.", mPort)); } mIsOpen = false; mAsyncServer = null; asyncServer.close(); // Close the new connection feed as well. mNewConnectionFeed.close(); if (sLogger.isLoggable(Level.INFO)) { sLogger.info( String.format( "Service closed on port %d.", mPort)); } } return; } // end of close() } // end of class EServer // // CHANGE LOG // $Log: EServer.java,v $ // Revision 1.5 2008/05/08 23:28:18 charlesr // Corrected for compiler warnings. // // Revision 1.4 2007/11/15 14:07:44 charlesr // Updated for new message header. // // Revision 1.3 2006/10/01 18:10:33 charlesr // Wait for client thread to start. // // Revision 1.2 2006/07/09 18:56:41 charlesr // Moved from multi-threaded sockets to select-based sockets. // // Revision 1.1 2006/01/01 22:02:03 charlesr // Initial revision //




© 2015 - 2025 Weber Informatics LLC | Privacy Policy