/**
* a software bus package
*
* @author Yannick Jestin yannick.jestin&enac.fr
* @author http://www.tls.cena.fr/products/ivy/
*
* (c) CENA 1998-2004
* (c) ENAC 2005-2011
*
*
*Ivy bus = new Ivy("Dummy agent","ready",null);
*bus.bindMsg("(.*)",myMessageListener);
*bus.start(getDomain(null));
*
*
* CHANGELOG:
* 1.2.14
* - added a lock mechanism to be sure that once a connexion has been
* initiated, the ready message will be sent before stopping the bus
* now: Ivy b = new Ivy(...); b.sendMsg("coucou"); b.stop(); should
* send messages (at least the ready message) if there is a connexion
* attempt made before b.stop() is effective. To be sure, there is a 200ms
* delay before b.stop() can be effective (the Threads stopped, the sockets
* closed)
* - reintroduced a mechanism to allow the drop of a double connexion
* attempt
* - removed protected methods from javadoc
* - switch to apache fop + docbook for documentation
* - added sypport to the Swing Dispatch Thread in the bindAsyncMsg api
* (this breaks the former API, this is BAAAAAD). Use BindType.SWING as the
* latter argument
* - javadoc updated
* - appName gone private, with a protected accessor
* - add a lockApp synchronization for application socket control
* - use of stringbuffers to concatenate strings, instead of using +, which
* could lead to a quadractic cost in the number of iteraction (the growing
* string was recopied in each iteration)
* - throws RuntimeException instead of System.exit(), allows code reuse
* - ready message is set to appName + " READY" if null has been provided
* - switch from gnu regexp (deprecated) to the built in java regexp
* - when possible, move the regexp Pattern.compile in static areas, to avoid multiple
* calls
* - add generic types to declarations
* - fxed a potential null pointer dereference on quit
* - lowercase CheckRegexp to checkRegexp (bad practice, thanks to FindBugs)
* - recopy the filter String[] in setfilter, to avoid exposing internal
* representation (unsafe operation)
* 1.2.13:
* - adds support for RESyntaxException
* 1.2.12:
* - directMessage goes protected
* 1.2.9:
* - introducing setFilter()
* - introducing IVYRANGE in to allow the bus service socket to start on a
* specific port range ( think of firewalls ), using java -DIVYRANGE=4000-5000 e.g.
* 1.2.8:
* - addclient and removeclient going synchronized
* - domainaddr goes protected in Domain ( gij compatibility )
* - checks if (Client)e.nextElement() each time we want to ...
* Multithreaded Enumerations ..., should fix [YJnul05]
* - added getDomainArgs(String,String[]) as a facility to parse the
* command line in search of a -b domain
* - added getWBUId(), un function returning a string ID to perform
* queries, computed strings look like IDTest0:1105029280616:1005891134
* - empties the watchers vector after a stop(), and handles the "stopped"
* better, FIXES FJ's bugreport stop/start
* 1.2.7:
* - minor fixes for accessing static final values
* 1.2.6:
* - added serial numbers for traceDebug
* - changed the semantic of -b a,b:port,c:otherport if no port is
* specified for a, it take the port from the next one. If none is
* specified, it takes DEFAULT_PORT
* - no more asynchronous sending of message ( async bind is ok though )
* because the tests are sooooo unsuccessful
* - use addElement/removeElement instead of add/remove is registering
* threads ( jdk1.1 backward compatibility )
* 1.2.5:
* - protection of newlines
* 1.2.4:
* - added an accessor for doSendToSelf
* - waitForMsg() and waitForClient() to make the synchronization with
* other Ivy agents easier
* - with the bindAsyncMsg() to subscribe and perform each callback in a
* new Thread
* - bindMsg(regexp,messagelistener,boolean) allow to subscribe with a
* synchrone/asynch exectution
* - API change, IvyException raised when \n or \0x3 are present in bus.sendMsg()
* - bindListener are now handled
* - removeApplicationListener can throw IvyException
* - bus.start(null) now starts on getDomain(null), first the IVYBUS
* property, then the DEFAULT_DOMAIN, 127:2010
* - bindMsg() now throws an IvyException if the regexp is invalid !!!
* BEWARE, this can impact lots of programs ! (fixes J007)
* - no more includes the "broadcasting on " in the domain(String) method
* - new function sendToSelf(boolean) allow us to send messages to
* ourselves
* 1.2.3:
* - adds a IVYBUS property to propagate the domain once set. This way,
* children forked through Ivy java can inherit from the current value.
* - adds synchronized flags to allow early disconnexion
* 1.2.2:
* added the String domains(String d) function, in order to display the
* domain list
* 1.2.1:
* bus.start(null) now starts on DEFAULT_DOMAIN. ( correction 1.2.4 This was not true.)
* added the getDomains in order to correctly display the domain list
* checks if the serverThread exists before interrupting it
* no has unBindMsg(String)
* 1.2.0:
* setSoTimeout is back on the server socket
* added a regression test main()
* clients is now a Hashtable. the deletion now works better
* getIvyClientsByName allows the research of IvyClient by name
* getDomain doesnt throw IvyException anymore
* removed the close() disconnect(IvyClient c). Fixes a big badaboum bug
* getDomain becomes public
* adding the sendToSelf feature
* fixed the printStackTrace upon closing of the ServerSocket after a close()
*/
package fr.dgac.ivy;
import java.net.*;
import java.io.*;
import java.util.*;
import gnu.getopt.Getopt;
import java.util.regex.*;
public class Ivy implements Runnable {
/**
* the protocol version number.
*/
public static final int PROTOCOLVERSION = 3;
public static final int PROTOCOLMINIMUM = 3;
private static final int GRACEDELAY = 200; // in milliseconds
/**
* the port for the UDP rendez vous, if none is supplied.
*/
public static final int DEFAULT_PORT = 2010;
/**
* the domain for the UDP rendez vous.
*/
public static final String DEFAULT_DOMAIN = "127.255.255.255:" + DEFAULT_PORT;
/**
* the library version, useful for development purposes only, when java is
* invoked with -DIVY_DEBUG
*/
public static final String LIBVERSION ="1.2.14";
public static final int TIMEOUTLENGTH = 1000;
private String appName;
private int applicationPort; /* Application port number */
private String ready_message = null;
private boolean doProtectNewlines = false;
private SelfIvyClient selfIvyClient;
private Object lockApp = new Object();
private boolean debug;
private ServerSocket app;
private Vector watchers = new Vector();
private volatile Thread serverThread; // to ensure quick communication of the end
private Hashtable clients = new Hashtable();
private Hashtable half = new Hashtable();
private Vector ivyApplicationListenerList = new Vector();
private Vector ivyBindListenerList = new Vector();
private Vector sendThreads = new Vector();
private String[] filter = null;
private boolean stopped = true;
private boolean starting = false;
protected Object readyToSend = new Object();
private boolean doSendToSelf = false;
private static int serial = 0;
private int myserial = serial++;
private static long current = System.currentTimeMillis();
private static java.util.Random generator = new java.util.Random(current*(serial + 1));
private String watcherId = null;
private static Pattern rangeRE; // tcp range min and max
private static Pattern bounded;
private static final Object lock = new Object();
/**
* Readies the structures for the software bus connexion.
*
* All the dirty work is done un the start() method
* @see #start
* @param name The name of your Ivy agent on the software bus
* @param message The hellow message you will send once ready. It can be
* null, in which case "appname READY" will be the default
* @param appcb A callback handling the notification of connexions and
* disconnections, (may be null for most agents)
*/
public Ivy(final String name, final String message, final IvyApplicationListener appcb) {
appName = name;
ready_message = (message == null) ? name + " READY" : message;
debug =
(System.getProperty("IVY_DEBUG") != null)
|| (System.getProperty("IVY_DEBUG") != null);
if ( appcb != null ) {
ivyApplicationListenerList.addElement( appcb );
}
selfIvyClient = new SelfIvyClient(this , name);
}
/**
* Waits for a message to be received.
*
* @since 1.2.4
* @param regexp the message we're waiting for to continue the main thread.
* @param timeout in millisecond, 0 if infinite
* @throws IvyException if something bad happens
* @return the IvyClient who sent the message, or null if the timeout is
* reached
*/
public final IvyClient waitForMsg(final String regexp , final int timeout) throws IvyException {
Waiter w = new Waiter(timeout);
int re = bindMsg(regexp , w);
IvyClient ic = w.waitFor();
unBindMsg(re);
return ic;
}
/**
* Waits for an other IvyClient to join the bus.
*
* @since 1.2.4
* @param name the name of the client we're waiting for to continue the main thread.
* @param timeout in millisecond, 0 if infinite
* @throws IvyException if something bad happens
* @return the first IvyClient with the name or null if the timeout is
* reached
*/
public final IvyClient waitForClient(final String name , final int timeout) throws IvyException {
IvyClient ic;
if (name == null) {
throw new IvyException("null name given to waitForClient");
}
// first check if client with the same name is on the bus
ic = alreadyThere(clients , name);
if (ic != null) {
return ic;
}
// if not enter the waiting loop
WaiterClient w = new WaiterClient(name , timeout , clients);
int i = addApplicationListener(w);
ic = w.waitForClient();
removeApplicationListener(i);
return ic;
}
/*
* since 1.2.8
*/
protected static IvyClient alreadyThere(final Hashtable c , final String name) {
for (IvyClient ic : c.values()) {
if ((ic != null)&&(name.compareTo(ic.getApplicationName()) == 0)) return ic;
}
return null;
}
/**
* connects the Ivy bus to a domain or list of domains.
*
* One thread (IvyWatcher) for each traffic rendezvous (either UDP broadcast or TCPMulticast)
* One thread (serverThread/Ivy) to accept incoming connexions on server socket
* a thread for each IvyClient when the connexion has been done
* @throws IvyException if there is a problem joining the bus
* @param domainbus a domain of the form 10.0.0:1234, A good practice is to
* sick to a null value, so that your agent will honor the IVY_BUS parameter
* given to the jvm (java -DIVYBUS= ... . Otherwise, you can provide some
* hard-coded value, similar to the
* netmask without the trailing .255. This will determine the meeting point
* of the different applications. Right now, this is done with an UDP
* broadcast. Beware of routing problems ! You can also use a comma
* separated list of domains.
*
* 1.2.8: goes synchronized. I don't know if it's really useful
*
*/
public final void start(final String domainbus) throws IvyException {
if (!stopped) throw new IvyException("cannot start a bus that's already started");
setStarting(true); // this will remain true entil one of the PacketSenders has finished
stopped=false;
String db = domainbus;
if (db == null) {
db = getDomain(null);
}
Properties sysProp = System.getProperties();
sysProp.put("IVYBUS" , db);
String range = (String)sysProp.get("IVYRANGE");
Matcher match;
if ((range != null)&&(match = rangeRE.matcher(range)).matches()) {
int rangeMin = Integer.parseInt(match.group(1));
int rangeMax = Integer.parseInt(match.group(2));
int index = rangeMin;
traceDebug("trying to allocate a TCP port between " + rangeMin + " and " + rangeMax);
boolean allocated = false;
while (!allocated) try {
if (index>rangeMax) throw new IvyException("no available port in IVYRANGE" + range );
synchronized (lockApp) {
app = new ServerSocket(index);
app.setSoTimeout(TIMEOUTLENGTH);
applicationPort = app.getLocalPort();
}
allocated = true;
} catch (BindException e) {
index++;
} catch (IOException e) {
throw new IvyException("can't open TCP service socket " + e );
}
}
else try {
synchronized (lockApp) {
app = new ServerSocket(0);
app.setSoTimeout(TIMEOUTLENGTH);
applicationPort = app.getLocalPort();
}
} catch (IOException e) {
throw new IvyException("can't open TCP service socket " + e );
}
traceDebug("lib: " + LIBVERSION + " protocol: " + PROTOCOLVERSION + " TCP service open on port " + applicationPort);
Domain[] d = parseDomains(db);
if (d.length == 0) throw new IvyException("no domain found in " + db);
watcherId = getWBUId().replace(' ' , '*'); // no space in the watcherId
// readies the rendezvous : an IvyWatcher (thread) per domain bus
for (Domain dom: d) watchers.addElement(new IvyWatcher(this , dom.domainaddr , dom.port));
serverThread = new Thread(this);
serverThread.start();
// sends the broadcasts and listen to incoming connexions
for (IvyWatcher iw: watchers) iw.doStart();
}
protected final Domain[] parseDomains(final String domainbus) {
StringTokenizer st = new StringTokenizer(domainbus , ",");
Domain[] d = new Domain[st.countTokens()];
int index = 0;
while ( st.hasMoreTokens()) {
String s = st.nextToken();
try {
d[index++] = new Domain(IvyWatcher.getDomain(s) , IvyWatcher.getPort(s));
} catch (IvyException ie) {
// do nothing
ie.printStackTrace();
}
}
// fixes the port values ...
int lastport = Ivy.DEFAULT_PORT;
for (index--; index >= 0; index--) {
Domain dom = d[index];
if (dom.port == 0) dom.port = lastport;
lastport = dom.port;
}
return d;
}
private void waitForRemote(String s) {
try {
while (starting==true) {
Thread.sleep(GRACEDELAY);
traceDebug("I'm waiting before "+s+", a starting tread is in progress");
}
} catch (InterruptedException ie) {
// should not happen, and it's not a problem anyway
}
}
/**
* disconnects from the Ivy bus.
*/
public final void stop() {
waitForRemote("stopping");
if (stopped) return;
stopped = true;
serverThread = null;
traceDebug("beginning stopping");
try {
// stopping the serverThread
Thread t = serverThread;
if (t != null) {
t.interrupt(); // The serverThread might be stopped even before having been created
// System.out.println("IZZZ joining " + t);
try { t.join(); } catch ( InterruptedException ie ) {
ie.printStackTrace();
}
}
synchronized (lockApp) { app.close(); }
// stopping the IvyWatchers
for (IvyWatcher iw: watchers) iw.doStop();
watchers.clear();
// stopping the remaining IvyClients
for (IvyClient c : clients.values()) {
if (c != null) {
c.close(true);
removeClient(c);
}
}
} catch (IOException e) {
traceDebug("IOexception Stop ");
}
traceDebug("end stopping");
}
/**
* Toggles the sending of messages to oneself, the remote client's
* IvyMessageListeners are processed first, and ourself afterwards.
* @param b true if you want to send the message to yourself. Default
* is false
* @since 1.2.4
*/
public final void sendToSelf(final boolean b) {
doSendToSelf = b;
}
/**
* do I send messsages to myself ?
* @return a boolean
* @since 1.2.4
*/
public final boolean isSendToSelf() {
return doSendToSelf;
}
/**
* selfIvyClient accesssor.
* @return our selfIvyClient
* @since 1.2.4
* @since 1.2.4
*/
public final SelfIvyClient getSelfIvyClient() {
return selfIvyClient;
}
/**
* Toggles the encoding/decoding of messages to prevent bugs related to the
* presence of a "\n".
* @param b true if you want to enforce encoding of newlines. Default
* is false. Every receiver will have to decode newlines
* @since 1.2.5
* The default escape character is a ESC 0x1A
*/
public final void protectNewlines(final boolean b) {
doProtectNewlines = b;
}
/**
* Performs a pattern matching according to everyone's regexps, and sends
* the results to the relevant ivy agents.
* @throws IvyException if there is a problem sending the message
* @param message A String which will be compared to the regular
* expressions of the different clients
* @return returns the number of messages actually sent
*/
public final int sendMsg(final String message) throws IvyException {
int count = 0;
waitForRemote("sending");
synchronized (lock) {
traceDebug("sending "+message);
String msg = message;
if (doProtectNewlines) msg = IvyClient.encode(message);
else if ( (msg.indexOf(IvyClient.newLineChar) != -1)||(msg.indexOf(IvyClient.endArgChar) != -1))
throw new IvyException("newline character not allowed in Ivy messages");
for ( IvyClient client : clients.values()) if (client != null) count += client.sendMsg(msg);
if (doSendToSelf) count+=selfIvyClient.sendSelfMsg(msg);
traceDebug("end sending "+message+" to "+count+" clients");
}
return count;
}
/**
* Subscribes to a regular expression.
*
* The callback will be executed with
* the saved parameters of the regexp as arguments when a message will sent
* by another agent. A program doesn't receive its own messages.
* Example:
*
the Ivy agent A performs
b.bindMsg("^Hello (*)",cb);
*
the Ivy agent B performs b2.sendMsg("Hello world");
*
a thread in A will uun the callback cb with its second argument set
* to a array of String, with one single element, "world"
* @param sregexp a perl regular expression, groups are done with parenthesis
* @param callback any objects implementing the IvyMessageListener
* interface, on the AWT/Swing framework
* @throws IvyException if there is a problem in the binding, be it regexp
* or network
* @return the id of the regular expression
*/
public final int bindMsg(final String sregexp , final IvyMessageListener callback ) throws IvyException {
return bindMsg(sregexp , callback , BindType.NORMAL);
}
/**
* Subscribes to a regular expression with asyncrhonous callback execution.
*
* Same as bindMsg, except that the callback will be executed in a separate
* thread each time.
* WARNING : there is no way to predict the order of execution
* of the * callbacks, i.e. a message received might trigger a callback before
* another one sent before
*
* @since 1.2.4
* @param sregexp a perl compatible regular expression, groups are done with parenthesis
* @param callback any objects implementing the IvyMessageListener
* interface, on the AWT/Swing framework
* @param type if set to NORMAL, it's a normal bind, if it's ASYNC, the
* callback will be created in a newly spawned Thread (Heavy ressources), if
* it's SWING, the callback will be deferred to the Swing Event Dispatch
* Tread
* @throws IvyException if there is a problem binding (network, regexp...)
* @return the int ID of the regular expression.
*/
public final int bindAsyncMsg(final String sregexp, final IvyMessageListener callback, BindType type ) throws IvyException {
return bindMsg(sregexp , callback , type);
}
/**
* Subscribes to a regular expression.
*
* The callback will be executed with
* the saved parameters of the regexp as arguments when a message will sent
* by another agent. A program doesn't receive its own messages,
* except if sendToSelf() is set to true.
* Example:
*
the Ivy agent A performs
b.bindMsg("^Hello (*)",cb);
*
the Ivy agent B performs b2.sendMsg("Hello world");
*
a thread in A will uun the callback cb with its second argument set
* to a array of String, with one single element, "world"
* @since 1.2.4
* @param sregexp a perl regular expression, groups are done with parenthesis
* @param callback any objects implementing the IvyMessageListener
* interface, on the AWT/Swing framework
* @param type if NORMAL (default) it's a normal bind, if ASYNC, each callback will be run in a separate thread, if SWING, the callback will be deferred to the Swing Event Dispatch Thread
* default is NORMAL
* @throws IvyException if there is a problem binding (regexp, network)
* @return the id of the regular expression
*/
public final int bindMsg(final String sregexp , final IvyMessageListener callback , final BindType type ) throws IvyException {
// adds the regexp to our collection in selfIvyClient
int key = selfIvyClient.bindMsg(sregexp , callback , type);
// notifies the other clients this new regexp
for (IvyClient c : clients.values() ) if (c != null) c.sendRegexp(key , sregexp);
return key;
}
/**
* Subscribes to a regular expression for one time only, useful for
* requests, in cunjunction with getWBUId().
*
* The callback will be executed once and only once, and the agent will
* unsubscribe
* @since 1.2.8
* @param sregexp a perl regular expression, groups are done with parenthesis
* @param callback any objects implementing the IvyMessageListener
* interface, on the AWT/Swing framework
* @throws IvyException if there is a problem during the binding
* @return the id of the regular expression
*/
public final int bindMsgOnce(final String sregexp, final IvyMessageListener callback ) throws IvyException {
Once once = new Once(callback);
int id = bindMsg(sregexp , once);
once.setRegexpId(id);
return id;
}
/**
* unsubscribes a regular expression using the id provided at bind time.
*
* @param id the id of the regular expression, returned when it was bound
* @throws IvyException if the id is not valid anymore
*/
public final void unBindMsg(final int id) throws IvyException {
selfIvyClient.unBindMsg(id);
for (IvyClient ic : clients.values() ) if (ic != null) ic.delRegexp(id );
}
/**
* unsubscribes a regular expression based on its string.
*
* @return a boolean, true if the regexp existed, false otherwise or
* whenever an exception occured during unbinding
* @param re the string for the regular expression
*/
public final boolean unBindMsg(final String re) { return selfIvyClient.unBindMsg(re); }
/**
* adds a bind listener to a bus.
* @param callback is an object implementing the IvyBindListener interface
* @return the id of the bind listener, useful if you wish to remove it later
* @since 1.2.4
*/
public final int addBindListener(final IvyBindListener callback){
ivyBindListenerList.addElement(callback);
return ivyBindListenerList.indexOf(callback);
}
/**
* removes a bind listener.
* @param id the id of the bind listener to remove
* @throws IvyException if id is not known
* @since 1.2.4
*/
public final void removeBindListener(final int id) throws IvyException {
try {
ivyBindListenerList.removeElementAt(id);
} catch (ArrayIndexOutOfBoundsException aie) {
throw new IvyException(id + " is not a valid Id");
}
}
/**
* adds an application listener to a bus.
* @param callback is an object implementing the IvyApplicationListener
* interface
* @return the id of the application listener, useful if you wish to remove
* it later
*/
public synchronized final int addApplicationListener(final IvyApplicationListener callback){
ivyApplicationListenerList.addElement(callback);
return ivyApplicationListenerList.indexOf( callback );
}
/**
* removes an application listener.
* @param id the id of the application listener to remove
* @throws IvyException if there is no such id
*/
public synchronized final void removeApplicationListener(final int id) throws IvyException {
try {
ivyApplicationListenerList.removeElementAt(id);
} catch (ArrayIndexOutOfBoundsException aie) {
throw new IvyException(id + " is not a valid Id");
}
}
/**
* sets the filter expression.
* @param f the extensive list of strings beginning the messages
* @since 1.2.9
*
* once this filter is set, when a client subscribes to a regexp of the
* form "^dummystring...", there is a check against the filter list. If no
* keyword is found to match, the binding is just ignored.
*/
public final synchronized void setFilter(final String[] f){
filter = java.util.Arrays.copyOf(f , f.length);
}
static {
// compiles the static regexps
try {
rangeRE = Pattern.compile("(\\d+)-(\\d+)"); // tcp range min and max
bounded = Pattern.compile("^\\^([a-zA-Z0-9_-]+).*");
} catch ( PatternSyntaxException res ) {
res.printStackTrace();
System.out.println("Regular Expression bug in Ivy source code ... bailing out");
}
}
/**
* checks the "validity" of a regular expression if a filter has been set.
* @since 1.2.9
* @param exp a string regular expression
* TODO must it be synchronized ( RE was not threadsafe, java regexp is )
*/
public final boolean checkRegexp(final String exp) {
if (filter == null) return true; // there's no message filter
Matcher m = bounded.matcher(exp);
if (!m.matches()) return true; // the regexp is not bounded
//System.out.println("the regexp is bounded, "+bounded.getParen(1));
// else the regexp is bounded. The matching string *must* be in the filter
String prems = m.group(1);
for (String f: filter) if (f.compareTo(prems) == 0) return true;
// traceDebug(" classFilter ["+filter[i]+"] vs regexp ["+prems+"]");
return false;
}
// a private class used by bindMsgOnce, to ensure that a callback will be
// executed once, and only once
private class Once implements IvyMessageListener {
private boolean received = false;
private int id = -1;
private IvyMessageListener ocallback = null;
Once(final IvyMessageListener callback){ ocallback = callback; }
synchronized void setRegexpId(final int fid){ id = fid; }
public void receive(final IvyClient ic , final String[] args){
synchronized(Once.this) {
// synchronized because it will most likely be called
// concurrently, and I *do* want to ensure that it won't
// execute twice
if (received||(ocallback == null)||(id == -1)) return;
received = true;
try { Ivy.this.unBindMsg(id); } catch (IvyException ie) { ie.printStackTrace(); }
ocallback.receive(ic , args);
}
}
}
/* invokes the application listeners upon arrival of a new Ivy client */
protected synchronized final void clientConnects(final IvyClient client){
for (IvyApplicationListener ial : ivyApplicationListenerList) ial.connect(client);
}
/* invokes the application listeners upon the departure of an Ivy client */
protected synchronized final void clientDisconnects(final IvyClient client){
for (IvyApplicationListener ial : ivyApplicationListenerList) ial.disconnect(client);
}
/* invokes the bind listeners */
protected final void regexpReceived(final IvyClient client , final int id , final String sregexp){
for (IvyBindListener ibl : ivyBindListenerList) ibl.bindPerformed(client , id , sregexp);
}
/* invokes the bind listeners */
protected final void regexpDeleted(final IvyClient client , final int id , final String sregexp){
for (IvyBindListener ibl : ivyBindListenerList) ibl.unbindPerformed(client , id , sregexp);
}
/*
* invokes the application listeners when we are summoned to die
* then stops
*/
protected synchronized final void dieReceived(final IvyClient client , final int id , final String message){
for (IvyApplicationListener ial : ivyApplicationListenerList) ial.die(client , id , message);
}
/* invokes the direct message callbacks */
protected synchronized final void directMessage(final IvyClient client , final int id , final String msgarg ){
for (IvyApplicationListener ial : ivyApplicationListenerList) ial.directMessage(client , id, msgarg);
}
/**
* gives the (Vectored) list of IvyClient at a given instant.
* @return a vector of IvyClients
*/
public final Vector getIvyClients() {
Vector v = new Vector();
for (IvyClient ic : clients.values() ) if (ic != null) v.addElement(ic);
return v;
}
/**
* gives a list of IvyClient with the name given in parameter.
*
* @param name The name of the Ivy agent you're looking for
* @return a vector of IvyClients
*/
public final Vector getIvyClientsByName(final String name) {
Vector v = new Vector();
String icname;
for (IvyClient ic : clients.values() ) {
if ( (ic == null)||((icname = ic.getApplicationName()) == null) ) break;
if (icname.compareTo(name) == 0) v.addElement(ic);
}
return v;
}
/**
* returns the domain bus.
*
* @param domainbus if non null, returns the argument
* @return It returns domainbus, if non null,
* otherwise it returns the IVYBUS property if non null, otherwise it
* returns Ivy.DEFAULT_DOMAIN
*/
public static final String getDomain(final String domainbus) {
String db = null;
db = domainbus;
if ( db == null ) db = System.getProperty("IVYBUS");
if ( db == null ) db = DEFAULT_DOMAIN;
return db;
}
/**
* returns the domain bus.
*
* @since 1.2.8
* @param progname The name of your program, for error message
* @param args the String[] of arguments passed to your main()
* @return returns the domain bus, ascending priority : ivy default bus, IVY_BUS
* property, -b domain on the command line
*/
public static final String getDomainArgs(final String progname, final String[] args) {
Getopt opt = new Getopt(progname , args , "b:");
int c;
if ( ((c = opt.getopt()) != -1) && c == 'b' ) return opt.getOptarg();
return getDomain(null);
}
/**
* returns a "wana be unique" ID to make requests on the bus.
*
* @since 1.2.8
* @return returns a string wich is meant to be noisy enough to be unique
*/
public final String getWBUId() {
return "ID<" + appName + myserial + ":" + nextId() + ":" + generator.nextInt() + ">";
}
private synchronized long nextId() { return current++; }
/**
* prints a human readable representation of the list of domains.
*
* @since 1.2.9
*/
public String domains(String toparse) {
StringBuffer s = new StringBuffer();
Ivy.Domain[] d = parseDomains(toparse);
for (Ivy.Domain dd : d) s.append(dd.getDomainaddr() + ":" + dd.getPort() + " ");
return s.toString();
}
/////////////////////////////////////////////////////////////////:
//
// Protected methods
//
/////////////////////////////////////////////////////////////////:
protected IvyClient createIvyClient(Socket s , int port, boolean domachin) throws IOException {
setStarting(true); // this one will stop when the client has finished starting
IvyClient i = new IvyClient(this , s , port , domachin);
i.doStart();
return i;
}
protected synchronized void removeClient(IvyClient c) {
synchronized(lock) {
clients.remove(c.getClientKey());
traceDebug("removed " + c + " from clients: " + getClientNames(clients));
}
}
protected synchronized void handShake(IvyClient c) {
synchronized(lock) {
removeHalf(c);
if (clients == null||c == null) return;
// TODO check if it's not already here !
IvyClient peer = searchPeer(c);
if ((peer == null) || peer.distanceTo(c)>0 ){
clients.put(c.getClientKey() , c);
setStarting(false);
traceDebug("added " + c + " in clients: " + getClientNames(clients));
} else {
traceDebug("not adding "+c+" in clients, double connexion detected, removing lowest one");
try {
c.close(false);
} catch (IOException ioe) {
// TODO
}
}
}
}
protected synchronized void addHalf(IvyClient c) {
synchronized(lock){ half.put(c.getClientKey() , c); }
traceDebug("added " + c + " in half: " + getClientNames(half));
}
protected synchronized void removeHalf(IvyClient c) {
synchronized(lock) {
if (half == null||c == null) return;
half.remove(c.getClientKey());
}
traceDebug("removed " + c + " from half: " + getClientNames(half));
}
/*
private synchronized boolean shouldIleave(IvyClient ic) {
traceDebug("looking for " + ic + " in " + getClientNames(half) + " and " + getClientNames(clients));
IvyClient peer = searchPeer(ic);
if (peer == null) return false;
boolean shoulda = peer.distanceTo(ic)>0;
traceDebug(ic + " " + ic.toStringExt() + ((shoulda) ? " must leave " : " must not leave"));
traceDebug(peer + " " + peer.toStringExt() + ((!shoulda) ? " must leave " : " must not leave"));
return shoulda;
}
*/
private synchronized IvyClient searchPeer(IvyClient ic) {
synchronized(lock) {
//for (Enumeration e = half.elements(); e.hasMoreElements(); ) {
// peer = e.nextElement();
// if ((peer != null)&&(peer.equals(ic))) return peer;
// }
for (IvyClient peer : clients.values()) if ((peer != null)&&(peer.equals(ic))) return peer;
}
return null;
}
/*
* the service socket thread reader main loop
*/
public void run() {
traceDebug("service thread started"); // THREADDEBUG
Thread thisThread = Thread.currentThread();
Socket socket = null;
while ( thisThread == serverThread ){
try {
synchronized (this) {
//System.out.println("DEBUG stopped: "+stopped);
if ((thisThread != serverThread)||stopped) break; // early disconnexion
}
synchronized (lockApp) {
socket = app.accept(); // TODO I can't synchronize on (this) in the run
}
synchronized (this) {
if ((thisThread != serverThread)||stopped) break; // early disconnexion
createIvyClient(socket , 0 , true); // the peer called me
}
} catch (InterruptedIOException ie) {
// traceDebug("server socket was interrupted. good");
if (thisThread != serverThread) break;
} catch( IOException e ) {
if (serverThread == thisThread) {
traceDebug("Error IvyServer exception: " + e.getMessage());
System.out.println("Ivy server socket reader caught an exception " + e.getMessage());
System.out.println("this is probably a bug in your JVM ! (e.g. blackdown jdk1.1.8 linux)");
throw new RuntimeException();
} else {
traceDebug("my server socket has been closed");
}
}
}
traceDebug("service thread stopped"); // THREADDEBUG
}
protected String getAppName() { return appName; }
protected int getAppPort() { return applicationPort; }
protected String getReadyMessage() { return ready_message; }
protected boolean getProtectNewlines() { return doProtectNewlines; }
protected void setStarting(boolean s) {
synchronized(readyToSend) {
traceDebug("setStarting "+s);
starting = s;
}
}
protected String getWatcherId() { return watcherId; }
protected int getSerial() { return myserial; }
private void traceDebug(String s){
if (debug) System.out.println("-->Ivy[" + myserial + "]<-- " + s);
}
// stuff to guarantee that all the treads have left
synchronized void registerThread(Thread t) { sendThreads.addElement(t); }
synchronized void unRegisterThread(Thread t) { sendThreads.removeElement(t); }
synchronized Thread getOneThread() {
if (sendThreads.size() == 0) return null;
return (Thread) sendThreads.firstElement();
}
// a small private method for debbugging purposes
private String getClientNames(Hashtable t) {
StringBuffer s = new StringBuffer();
s.append("(");
for (IvyClient ic : t.values() ) if (ic != null) s.append(ic.getApplicationName() + ",");
s.append(")");
return s.toString();
}
private static class Domain {
private String domainaddr;
private int port;
public Domain(String ddomainaddr , int dport) { this.domainaddr = ddomainaddr;this.port = dport; }
public String toString() { return domainaddr + ":" + port; }
public String getDomainaddr() { return domainaddr; }
public int getPort() { return port; }
}
} // class Ivy