/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package org.safs.sockets; import java.io.CharArrayWriter; import java.io.InvalidObjectException; import java.util.Properties; import java.util.Vector; /** * This abstract class implements the necessary Threading of a {@link SocketProtocol}. * The class provides the implementation needed by processes running on * Windows, *nix, or Mac to control a remote client complying with this SocketProtocol. * <p> * Concrete subclasses must implement the abstract processProtocolMessage(String) in order * to parse and process the messages received. * <p> * Currently, this server expects remote TCP services to be accepting connections on port 2410 * as required by the underlying SocketProtocol. * <p> * Currently, this server uses port 2411 to contact and attempt a connection to those remote * TCP services as required by the underlying SocketProtocol. * <p> * Both sides eventually need to be able to use a broader range of ports to prevent conflicts with * other system resources. * <p> * There is an initial handshake or verification that occurs between this remote controller server and the * on-device Service to confirm the device port owners conform to the SocketProtocol. * * @see SocketProtocol * @see SocketProtocol#MSG_PROTOCOL_VERSION_QUERY */ public abstract class AbstractProtocolRunner implements Runnable, ConnectionListener, DebugListener{ private int target_protocol = 1; // SAFS Protocol Runner version /** set to false to disable debug logging and improve performance. */ public boolean _debugEnabled = true; /** * Convenience routine to route internal debug messages to registered DebugListeners. * @param text */ protected void debug(String text){ onReceiveDebug(text); } /** All Listeners registered with this instance--whether they be simple NamedListeners, * DebugListeners, or ConnectionListeners. */ protected Vector runnerlisteners = new Vector(); /** * Our running thread monitors this value to know whether or not it should continue * its looping execution or shut itself down. * @see #shutdownThread() */ private boolean shutdownThread = false; /** * The underlying SocketProtocol instance performing the actual TCP communication. */ public SocketProtocol protocolserver = null; /** "AbstractRunner" * The name of this NamedListener, */ private String runnerName = "AbstractRunner"; /** * Default no-op constructor setting using all defaults. * This creates a default SocketProtocol instance running in local controller mode with * this class added as a registered Debug and Connection Listener. * <p> * Subclass implementation should change any desired remote hostname/port settings and add * any other Listeners prior to starting the Runnable thread. * @see SocketProtocol#SocketProtocol(NamedListener) */ public AbstractProtocolRunner(){ protocolserver = new SocketProtocol(this); } /** * Add any class or subclass of NamedListener to the registered Listeners list. * This allows the chaining or pass-thru of notifications from the underlying SocketProtocol * object to the registered Listeners of this class. * @param listener * @return true if the Listener was successfully registered. false if the Listener was * already registered. * @see NamedListener * @see DebugListener * @see ConnectionListener */ public boolean addListener(NamedListener listener){ if(! runnerlisteners.contains(listener)){ runnerlisteners.add(listener); return true; } return false; } /** * Remove a Listener from the list of registered Listeners for this instance. * @param listener * @return true if the Listener was successfully removed. false if the Listener was * not previously registered. * @see NamedListener * @see DebugListener * @see ConnectionListener */ public boolean removeListener(NamedListener listener){ if(runnerlisteners.contains(listener)){ runnerlisteners.remove(listener); return true; } return false; } /** * Required Runnable interface for the Threaded execution of the underlying SocketProtocol. * This thread implements the required sequence of events for both the local controller and * the remote client--both using a matching underlying SocketProtocol object--to connect through * TCP Sockets and continuously monitor and exchange UTF-8 messages until a request for * shutdown has been detected. * <p> * Subclasses of this abstract class would not normally change or override this method regardless * of being local controllers or remote clients. This routine handles both through the SocketProtocol * object. * <p> * The thread constantly loops performing the following tasks: * <p> * <ol> * <li>if not connected to a remote SocketProtocol, attempt to locate and connect to it. * <li>check to see if the remote SocketProtocol has sent any valid content we need to dispatch. * <li>if so, dispatch the content via {@link #processProtocolMessage(String)} * <li>if not shutdown, loop again. * </ol> * <p> * When exiting the loop due to a shutdown, all registered ConnectionListeners will be notified * via {@link ConnectionListener#onReceiveLocalShutdown(int)} that a "normal" shutdown has occurred. * The thread will then attempt to finish by closing the SocketProtocol communication channels. * <p> * Note that <i>sending</i> messages from the local SocketProtocol to the remote SocketProtocol is * NOT handled in this thread. Once the remote connection is made, registered ConnectionListeners * are notified via {@link ConnectionListener#onReceiveConnection()}. Messages sent from the local * using class to the remote SocketProtocol are sent via {@link #sendProtocolMessage(String)} and * usually from a different Thread. * Consequently, the two-way communication should be considered asynchronous. Though it is expected * the local and remote clients will attempt to maintain whatever "synchronous" communication is * appropriate for their shared protocol implementation. * * @see #sendProtocolMessage(String) * @see SocketProtocol#closeProtocolRunners() * @see #shutdownThread() */ public void run(){ String message = null; // loop while(!shutdownThread){ // makeClientConnection if(!protocolserver.isConnected()){ protocolserver.connectProtocolRunners(); } // listen for remote messages if(protocolserver.isConnected()){ try{ message = protocolserver.waitForInput(25);} catch(Exception x){ message = null; } // route message to appropriate listener callbacks if(message != null && message.length() > 0){ processProtocolMessage(message); } }else{ // not yet connected try{Thread.sleep(100);}catch(Exception x){} } }//while for(int n = 0; n < runnerlisteners.size(); n++){ try{((ConnectionListener)runnerlisteners.get(n)).onReceiveLocalShutdown(SocketProtocol.STATUS_SHUTDOWN_NORMAL);} catch(Exception x){} } protocolserver.closeProtocolRunners(); } /** * Command the ProtocolRunner thread to shutdown. * If the thread is running, this will ultimately null out all communication * streams and close the active socket connection (if any) to the remote client. * @see #run()*/ public void shutdownThread(){ shutdownThread = true; } /** {@link NamedListener#getListenerName()} */ public String getListenerName() { return runnerName; } /** {@link NamedListener#setListenerName(String)} */ public void setListenerName(String name) { runnerName = name; } /** * Send all registered DebugListeners a Debug message received from a remote source. * If no DebugListeners are registered, then the message may be routed to System.out. * This will only happen if debug output is enabled--which it is by default. * @param message * @see #_debugEnabled * @see DebugListener#onReceiveDebug(String) */ public void onReceiveDebug(String message){ if(_debugEnabled){ boolean sent = false; for(int i = 0; i< runnerlisteners.size();i++){ try{ ((DebugListener)runnerlisteners.get(i)).onReceiveDebug(message); sent = true; } catch(ClassCastException e){/* not all listeners are appropriate */ } } if(!sent) System.out.println(message); } } /** * Notify all registered ConnectionListeners a 2-way SocketProtocol connection has been established. * If no ConnectionListeners are registered, then the notification may be routed to System.out. * @see ConnectionListener#onReceiveConnection() */ public void onReceiveConnection(){ boolean sent = false; for(int i = 0; i< runnerlisteners.size();i++){ try{ ((ConnectionListener)runnerlisteners.get(i)).onReceiveConnection(); sent = true; } catch(ClassCastException e){/* not all listeners are appropriate */ } } if(!sent) System.out.println("Protocol Runners Connected"); } /** * Notify all registered ConnectionListeners the 2-way SocketProtocol connection is shutting down * on our local side. * If no ConnectionListeners are registered, then the notification may be routed to System.out. * @param caus -- whether "normal", or not. * @see ConnectionListener#onReceiveLocalShutdown(int) * @see SocketProtocol#STATUS_SHUTDOWN_NORMAL * @see SocketProtocol#STATUS_SHUTDOWN_REMOTE_CLIENT */ public void onReceiveLocalShutdown(int cause){ boolean sent = false; for(int i = 0; i< runnerlisteners.size();i++){ try{ ((ConnectionListener)runnerlisteners.get(i)).onReceiveLocalShutdown(cause); sent = true; } catch(ClassCastException e){/* not all listeners are appropriate */ } } if(!sent) System.out.println("Processing a local shutdown notification: "+ SocketProtocol.getShutdownCauseDescription(cause)); } /** * Notify all registered ConnectionListeners the 2-way SocketProtocol connection is shutting down * from the remote side. * If no ConnectionListeners are registered, then the notification may be routed to System.out. * @param caus -- whether "normal", or not. * @see ConnectionListener#onReceiveRemoteShutdown(int) * @see SocketProtocol#STATUS_SHUTDOWN_NORMAL * @see SocketProtocol#STATUS_SHUTDOWN_REMOTE_CLIENT */ public void onReceiveRemoteShutdown(int cause){ boolean sent = false; for(int i = 0; i< runnerlisteners.size();i++){ try{ ((ConnectionListener)runnerlisteners.get(i)).onReceiveRemoteShutdown(cause); sent = true; } catch(ClassCastException e){/* not all listeners are appropriate */ } } if(!sent) System.out.println("Processing a remote shutdown notification: "+ SocketProtocol.getShutdownCauseDescription(cause)); } /** * Send an arbitrary message through our SocketProtocol. * Subclasses of AbstractProtocolRunner will usually define the message content and syntax * to be appropriate for whatever the application is trying to accomplish. That is, different * applications will send and receive different messages. * @param message * @return true if successfully sent * @throws InvalidObjectException from the underlying SocketProtocol if no connection has * yet been made. * @see SocketProtocol#sendResponse(String) */ public boolean sendProtocolMessage(String message)throws InvalidObjectException{ return protocolserver.sendResponse(message); } /** * Concrete implementations must insert the parsing and processing of the messages * received from the underlying SocketProtocol. * Subclasses of AbstractProtocolRunner will usually define the message content and syntax * to be appropriate for whatever the application is trying to accomplish. That is, different * applications will send and receive different messages. * @param message */ public abstract void processProtocolMessage(String message); /** * Send a shutdown command to the remote client. The format or syntax of this will be * implementation specific. * <p> * A remote client Protocol Runner would implement this as a do-nothing method. * * @return true if the message was successfully sent. */ public abstract boolean sendShutdown(); /** * Send the remote client a dispatch Properties message containing a Serialized * Properties object containing all the data needed for * the execution of the Dispatch. The format or syntax of this will be * implementation specific. * <p> * A remote client Protocol Runner would implement this as a do-nothing method. * * @param Properties trd to send * @return true if the message was sent successfully. */ public abstract boolean sendDispatchProps(Properties trd); /** * Send the remote client a dispatch file message with the filepath to a file that * should be readable by the remote client. The format or syntax of this will be * implementation specific. * <p> * A remote client Protocol Runner would implement this as a do-nothing method. * * @param filepath path to a remote client readable file. * @return true if the message was successfully sent. */ public abstract boolean sendDispatchFile(String filepath); }