/** * This method takes all the children of the passed-in category (both {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects and contained {@link * arlut.csd.ganymede.server.DBBaseCategory DBBaseCategory} objects) and makes copies under this. */ private void recurseDown(DBBaseCategory category, Hashtable baseHash, DBSchemaEdit editor) throws RemoteException { Vector<CategoryNode> children = category.getNodes(); DBObjectBase oldBase, newBase; DBBaseCategory oldCategory, newCategory; /* -- */ if (debug) { Ganymede.debug("** recurseDown"); if (editor == null) { Ganymede.debug("**#?!?!!! DBBaseCategory.recurseDown(): editor == null!!!"); } } for (CategoryNode node : children) { if (node instanceof DBObjectBase) { oldBase = (DBObjectBase) node; // a new copy, with the same objects under it newBase = new DBObjectBase(oldBase, editor); baseHash.put(newBase.getKey(), newBase); if (false) { Ganymede.debug( "Created newBase " + newBase.getName() + " in recursive category tree duplication"); } // we want this base to be added to the current end of this category addNodeAfter(newBase, null); if (false) { Ganymede.debug("Added " + newBase.getName() + " to new category tree"); } } else if (node instanceof DBBaseCategory) { oldCategory = (DBBaseCategory) node; newCategory = (DBBaseCategory) newSubCategory(oldCategory.getName()); newCategory.editor = editor; if (false) { Ganymede.debug( "Created newCategory " + newCategory.getName() + " in recursive category tree duplication"); } newCategory.recurseDown(oldCategory, baseHash, editor); } } }
public void run(int portNumber) { try { listen(portNumber); } catch (IOException ex) { Ganymede.logError(ex); } }
public void shutdown() { synchronized (lock) { shutdownRequested = true; } /* Now close the ServerSocket */ try { new Socket(sock.getInetAddress(), sock.getLocalPort()).close(); } catch (IOException ex) { Ganymede.stackTrace(ex); } }
public void createSession(String personaName) { try { /* Snag the appropriate Admin Persona from the database */ DBObject persona = (DBObject) ((DBObjectBase) Ganymede.db.get("Admin Persona")).get(personaName); /* If there is a user associated with this persona, snag it */ DBObject user = null; InvidDBField userField = (InvidDBField) persona.get("User"); if (userField != null) { user = (DBObject) userField.getVal(); } /* Now we have all we need to create the session */ session = new GanymedeSession(personaName, user, persona, false); } catch (RemoteException ex) { Ganymede.stackTrace(ex); return; } interp.set("session", session); }
/** This method emits this delta rec to a file */ public void emit(DataOutput out) throws IOException { DBObjectBase baseDef; DBObjectBaseField fieldDef; /* -- */ // write the object id baseDef = Ganymede.db.getObjectBase(invid.getType()); if (debug) { System.err.println("Emitting delta rec for invid " + invid.toString()); } invid.emit(out); // write the fieldrec count out.writeInt(fieldRecs.size()); if (debug) { System.err.println( "Emitting " + fieldRecs.size() + " field records for invid " + invid.toString()); } // now let's write out the fields fieldDeltaRec fdRec; Object value; for (int i = 0; i < fieldRecs.size(); i++) { fdRec = (fieldDeltaRec) fieldRecs.elementAt(i); // write out our field code.. this will be used by the loader // code to determine what kind of field this is, and what // kind of data types need to be loaded. if (debug) { System.err.println("Emitting fieldDeltaRec:\n\t" + fdRec.toString()); } out.writeShort(fdRec.fieldcode); // are we deleting? if (!fdRec.vector && fdRec.scalarValue == null) { // yes, we're deleting this field out.writeBoolean(true); continue; } // no, we're redefining this field out.writeBoolean(false); // write out our field type code.. this will be used by the loader // to verify that the schema hasn't undergone an incompatible // change since the journal was written. fieldDef = (DBObjectBaseField) baseDef.getField(fdRec.fieldcode); out.writeShort(fieldDef.getType()); if (fdRec.scalarValue != null) { out.writeBoolean(true); // scalar redefinition fdRec.scalarValue.emit(out); continue; } out.writeBoolean(false); // vector mod // write out what is being added to this vector if (debug) { System.err.println("====== Emitting " + fdRec.addValues.size() + " addition elements"); } if (fdRec.addValues == null) { out.writeInt(0); } else { out.writeInt(fdRec.addValues.size()); for (int j = 0; j < fdRec.addValues.size(); j++) { value = fdRec.addValues.elementAt(j); // we only support 3 vector field types if (value instanceof String) { out.writeUTF((String) value); } else if (value instanceof Invid) { ((Invid) value).emit(out); } else if (value instanceof IPwrap) { Byte[] bytes = ((IPwrap) value).address; out.writeByte(bytes.length); for (int k = 0; k < bytes.length; k++) { out.writeByte(bytes[k].byteValue()); } } else { Ganymede.debug("DBObjectDeltaRec.emit(): Error! Unrecognized element in vector!"); } } } // write out what is being removed from this vector if (fdRec.delValues == null) { out.writeInt(0); } else { out.writeInt(fdRec.delValues.size()); for (int j = 0; j < fdRec.delValues.size(); j++) { value = fdRec.delValues.elementAt(j); // we only support 3 vector field types if (value instanceof String) { out.writeUTF((String) value); } else if (value instanceof Invid) { Invid invid = (Invid) value; out.writeShort(invid.getType()); out.writeInt(invid.getNum()); } else if (value instanceof IPwrap) { Byte[] bytes = ((IPwrap) value).address; out.writeByte(bytes.length); for (int k = 0; k < bytes.length; k++) { out.writeByte(bytes[k].byteValue()); } } } } } }
/** This DBObjectDeltaRec constructor is used to load a delta record from a Journal stream. */ public DBObjectDeltaRec(DataInput in) throws IOException { short fieldcode; short typecode; boolean scalar; DBObjectBase baseDef; DBObjectBaseField fieldDef; String fieldName = null; String status = null; DBObject obj = null; /* -- */ status = "Reading invid"; boolean debug = true; try { invid = Invid.createInvid(in); baseDef = Ganymede.db.getObjectBase(invid.getType()); obj = Ganymede.db.viewDBObject(invid); if (debug) { System.err.println( "\n>*> Reading delta rec for " + baseDef.getName() + " <" + invid.getNum() + ">"); } status = "Reading field count"; int fieldcount = in.readInt(); if (debug) { System.err.println( ">> DBObjectDeltaRec(): " + fieldcount + " fields in on-disk delta rec."); } for (int i = 0; i < fieldcount; i++) { status = "\nReading field code for field " + i; if (debug) { System.err.println(status); } fieldcode = in.readShort(); fieldDef = (DBObjectBaseField) baseDef.getField(fieldcode); typecode = fieldDef.getType(); fieldName = fieldDef.getName(); status = "Reading deletion boolean for field " + i; if (in.readBoolean()) { // we're deleting this field if (debug) { System.err.println( "Reading field deletion record field (" + fieldName + ":" + fieldcode + ") for field " + i); } fieldRecs.addElement(new fieldDeltaRec(fieldcode, null)); continue; } // okay, we've got a field redefinition.. check the type // code to make sure we don't have an incompatible journal // entry status = "Reading type code for field " + i; if (in.readShort() != typecode) { throw new RuntimeException("Error, field type mismatch in journal file"); } // ok.. now, is it a total redefinition, or a vector delta record? status = "Reading scalar/vector delta boolean for field " + i; scalar = in.readBoolean(); if (scalar) { fieldDeltaRec f_r = null; status = "Reading field (" + fieldName + ":" + fieldcode + ") for field " + i; if (debug) { System.err.println(status); } f_r = new fieldDeltaRec(fieldcode, DBField.readField(obj, in, fieldDef)); fieldRecs.addElement(f_r); if (debug) { System.err.println("Value: " + f_r.toString()); } } else { // ok, we have a vector delta chunk fieldDeltaRec fieldRec = new fieldDeltaRec(fieldcode); Object value = null; // read in the additions status = "Reading vector addition count for field " + fieldName + "(" + i + ")"; if (debug) { System.err.println(status); } int size = in.readInt(); if (debug) { System.err.println(">> DBObjectDeltaRec(): reading " + size + " additions."); } for (int j = 0; j < size; j++) { // we only support 3 vector field types switch (typecode) { case STRING: status = "Reading string addition " + j + " for field " + i + ":" + fieldName; if (debug) { System.err.println(status); } value = in.readUTF(); break; case INVID: status = "Reading invid addition " + j + " for field " + i + ":" + fieldName; if (debug) { System.err.println(status); } value = Invid.createInvid(in); break; case IP: status = "Reading ip addition " + j + " for field " + i + ":" + fieldName; if (debug) { System.err.println(status); } byte bytelength = in.readByte(); Byte[] bytes = new Byte[bytelength]; for (int k = 0; k < bytelength; k++) { bytes[k] = Byte.valueOf(in.readByte()); } value = bytes; } fieldRec.addValue(value); } // read in the deletions status = "Reading vector deletion count for field " + i; if (debug) { System.err.println(status); } size = in.readInt(); if (debug) { System.err.println(">> DBObjectDeltaRec(): reading " + size + " deletions."); } for (int j = 0; j < size; j++) { // we only support 3 vector field types switch (typecode) { case STRING: status = "Reading string deletion " + j + " for field " + i + ":" + fieldName; value = in.readUTF(); break; case INVID: status = "Reading invid deletion " + j + " for field " + i + ":" + fieldName; value = Invid.createInvid(in); break; case IP: status = "Reading IP deletion " + j + " for field " + i + ":" + fieldName; byte bytelength = in.readByte(); Byte[] bytes = new Byte[bytelength]; for (int k = 0; k < bytelength; k++) { bytes[k] = Byte.valueOf(in.readByte()); } value = bytes; } fieldRec.delValue(value); } // and save this field fieldRecs.addElement(fieldRec); } } } catch (IOException ex) { System.err.println("DBObjectDeltaRec constructor: IOException in state " + status); Ganymede.logError(ex); throw ex; } }
/** * This method is used to remove a Category Node from under us. * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void removeNode(CategoryNode node) throws RemoteException { int i, index = -1; /* -- */ if (node == null) { throw new IllegalArgumentException("Can't remove a null node"); } // find our deletion point if (debug) { try { Ganymede.debug("DBBaseCategory (" + getName() + ").removeNode(" + node.getPath() + ")"); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("rmi local failure?" + ex.getMessage()); } } for (i = 0; i < contents.size(); i++) { if (debug) { try { Ganymede.debug(" examining: " + ((CategoryNode) contents.elementAt(i)).getPath()); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("rmi local failure?" + ex.getMessage()); } } if (contents.elementAt(i).equals(node)) { index = i; } } if (index == -1) { throw new IllegalArgumentException("can't delete a node that's not in the category"); } // remove our node from our content list contents.removeElementAt(index); if (false) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (!base.isEditing()) { System.err.println( "DBBaseCategory.removeNode(): " + base.getName() + " has a null editor!"); } else { System.err.println( "DBBaseCategory.removeNode(): " + base.getName() + " has a non-null editor!"); } } } // Sorry, kid, yer on your own now! node.setCategory(null); }
/** * This method can be used to move a Category from another Category to this Category, or to move a * Category around within this Category. * * @param catPath the fully specified path of the node to be moved * @param prevNodeName the name of the node that the catPath node is to be placed after in this * category, or null if the node is to be placed at the first element of this category * @see arlut.csd.ganymede.rmi.Category */ public synchronized void moveCategoryNode(String catPath, String prevNodeName) { if (debug) { System.err.println("DBBaseCategory.moveCategoryNode(" + catPath + "," + prevNodeName + ")"); } CategoryNode categoryNode; DBBaseCategory oldCategory; try { categoryNode = editor.getCategoryNode(catPath); oldCategory = (DBBaseCategory) categoryNode.getCategory(); } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException("wow, surprising remote local exception"); } if (oldCategory == this) { if (debug) { System.err.println("DBBaseCategory.moveCategoryNode(): moving node within category"); } contents.removeElement(categoryNode); } else { if (debug) { System.err.println( "DBBaseCategory.moveCategoryNode(): moving node from " + oldCategory.getPath() + " to " + getPath()); } try { oldCategory.removeNode(categoryNode); } catch (RemoteException ex) { throw new RuntimeException("Local category threw a remote exception.. ? " + ex); } } try { categoryNode.setCategory(this); } catch (RemoteException ex) { throw new RuntimeException("Local category node threw a remote exception.. ? " + ex); } if (prevNodeName == null) { contents.insertElementAt(categoryNode, 0); } else { for (int i = 0; i < contents.size(); i++) { CategoryNode cNode = (CategoryNode) contents.elementAt(i); try { if (cNode.getName().equals(prevNodeName)) { contents.insertElementAt(categoryNode, i + 1); return; } } catch (RemoteException ex) { } } throw new RuntimeException( "Couldn't move category node " + catPath + " after non-existent " + prevNodeName); } }
/** * This method is used to remove a Category Node from under us. * * @see arlut.csd.ganymede.rmi.Category */ public synchronized void removeNode(String name) throws RemoteException { int i, index = -1; CategoryNode node = null; /* -- */ if (name == null) { throw new IllegalArgumentException("Can't remove a null name"); } // find our deletion point if (debug) { Ganymede.debug("DBBaseCategory (" + getName() + ").removeNode(" + name + ")"); } for (i = 0; i < contents.size() && (index == -1); i++) { if (debug) { Ganymede.debug(" examining: " + contents.elementAt(i)); } node = (CategoryNode) contents.elementAt(i); try { if (node.getName().equals(name)) { index = i; } } catch (RemoteException ex) { throw new RuntimeException("caught remote: " + ex); } } if (index == -1) { throw new IllegalArgumentException("can't delete a name that's not in the category"); } else if (debug) { System.err.println("DBBaseCategory.removeNode(): found node " + node); if (node instanceof DBObjectBase) { System.err.println("DBBaseCategory.removeNode(): node is DBObjectBase"); } else if (node instanceof Base) { System.err.println("DBBaseCategory.removeNode(): node is Base"); } else if (node instanceof DBBaseCategory) { System.err.println("DBBaseCategory.removeNode(): node is DBBaseCategory"); } else if (node instanceof Category) { System.err.println("DBBaseCategory.removeNode(): node is Category"); } else { System.err.println("DBBaseCategory.removeNode(): node is <unrecognized>"); } } // remove our node from our content list contents.removeElementAt(index); if (debug) { if (node instanceof DBObjectBase) { DBObjectBase base = (DBObjectBase) node; if (!base.isEditing()) { System.err.println( "DBBaseCategory.removeNode(2): " + base.getName() + " has a null editor!"); } else { System.err.println( "DBBaseCategory.removeNode(2): " + base.getName() + " has a non-null editor!"); } } } // Sorry, kid, yer on your own now! node.setCategory(null); }
public void run() { try { OutputStream rawOutput = socket.getOutputStream(); InputStream rawInput = socket.getInputStream(); PrintWriter out = new PrintWriter(rawOutput, true); BufferedReader in = new BufferedReader(new InputStreamReader(rawInput)); String inputLine, outputLine; /* Check to make sure that this telnet session is from localhost */ InetAddress clientAddress = socket.getInetAddress(); if (!clientAddress.equals(java.net.InetAddress.getByName(Ganymede.serverHostProperty)) && !clientAddress.getHostAddress().equals("127.0.0.1")) { // Permission denied out.println(ts.l("run.denied_not_localhost")); out.flush(); socket.close(); return; } /* Check to see if logins are allowed */ String error = Ganymede.server.lSemaphore.increment(); if (error != null) { if (error.equals("shutdown")) { // "ERROR: The server is currently waiting to shut down. No logins will be accepted until // the server has restarted" out.print(ts.l("run.nologins_shutdown")); } else { // "ERROR: Can't log in to the Ganymede server.. semaphore disabled: {0}" out.print(ts.l("run.nologins_semaphore", error)); } out.print("\n"); out.flush(); socket.close(); return; } try { // "Username" out.print(ts.l("run.username")); out.print(": "); out.flush(); String loginName = in.readLine(); /* Telnet terminal codes */ /* IAC WILL ECHO */ byte[] echoOff = {(byte) 255, (byte) 251, (byte) 1}; /* IAC DO ECHO */ byte[] echoOffResponse = {(byte) 255, (byte) 253, (byte) 1}; /* IAC WONT ECHO */ byte[] echoOn = {(byte) 255, (byte) 252, (byte) 1}; /* IAC DONT ECHO */ byte[] echoOnResponse = {(byte) 255, (byte) 254, (byte) 1}; /* Holds the client response to each terminal code */ byte[] responseBuffer = new byte[3]; // "Password" out.print(ts.l("run.password")); out.print(": "); out.flush(); /* Disable client-side character echo while the user types in * the password */ rawOutput.write(echoOff); rawOutput.flush(); int chars_read = 0; while (chars_read < 3) { chars_read += rawInput.read(responseBuffer, chars_read, 3 - chars_read); } if (!Arrays.equals(responseBuffer, echoOffResponse)) { out.print("Your telnet client won't properly suppress character echo."); out.flush(); socket.close(); return; } String password = in.readLine(); /* Now re-enable client-side character echo so we can conduct * business as usual */ rawOutput.write(echoOn); rawOutput.flush(); chars_read = 0; while (chars_read < 3) { chars_read += rawInput.read(responseBuffer, chars_read, 3 - chars_read); } if (!Arrays.equals(responseBuffer, echoOnResponse)) { out.print("Your telnet client won't properly resume character echo."); out.flush(); socket.close(); return; } /* Authenticate the user */ int validationResult = Ganymede.server.validateAdminUser(loginName, password); /* A result of 3 means that this user has interpreter access * privileges. Anything else means that we give 'em the boot. */ if (validationResult != 3) { try { Thread.currentThread().sleep(3000); } catch (InterruptedException ex) { /* Move along */ } // "Permission denied." out.print(ts.l("run.denied")); out.print("\n"); out.flush(); socket.close(); return; } /* Send the HELO */ outputLine = protocol.processInput(null); out.print(outputLine); out.flush(); /* Setup the interpreter session variable */ protocol.createSession(loginName); /* Here is the read-eval-print loop */ while ((inputLine = in.readLine()) != null) { outputLine = protocol.processInput(inputLine); out.print(outputLine); out.flush(); if (outputLine.equals(JythonServerProtocol.doneString)) break; } out.close(); in.close(); socket.close(); } finally { Ganymede.server.lSemaphore.decrement(); /* Make sure to register the logout */ try { protocol.session.logout(); } catch (Exception e) { Ganymede.logError(e); // Move along } } } catch (IOException e) { Ganymede.logError(e); } }
/** * This method is used to scan the persistent log storage for log events that match invid and that * have occurred since sinceTime. * * @param invid If not null, retrieveHistory() will only return events involving this object * invid. * @param sinceTime if not null, retrieveHistory() will only return events occuring on or after * the time specified in this Date object. * @param beforeTime if not null, retrieveHistory() will only return events occurring on or before * the time specified in this Date object. * @param keyOnAdmin if true, rather than returning a string containing events that involved * <invid>, retrieveHistory() will return a string containing events performed on behalf * of the administrator with invid <invid>. * @param fullTransactions if true, the buffer returned will include all events in any * transactions that involve the given invid. if false, only those events in a transaction * directly affecting the given invid will be returned. * @param getLoginEvents if true, this method will return only login and logout events. if false, * this method will return no login and logout events. * @return A human-readable multiline string containing a list of history events */ public StringBuffer retrieveHistory( Invid invid, Date sinceTime, Date beforeTime, boolean keyOnAdmin, boolean fullTransactions, boolean getLoginEvents) { if (con == null) { throw new IllegalArgumentException("no connection to postgres database"); } StringBuffer buffer = new StringBuffer(); Date transDate = null; String prevTransID = null; String transAdmin = null; ResultSet rs = null; /* -- */ try { if (keyOnAdmin) { rs = queryEventsByAdmin(invid, sinceTime, beforeTime); } else if (fullTransactions) { rs = queryEventsByTransactions(invid, sinceTime, beforeTime); } else { rs = queryEvents(invid, sinceTime, beforeTime); } while (rs.next()) { long time = rs.getLong(1); String token = rs.getString(2); String adminName = rs.getString(3); String text = rs.getString(4); String transactionID = rs.getString(5); if (invid.getType() == SchemaConstants.UserBase) { if (token.equals("normallogin") || token.equals("normallogout") || token.equals("abnormallogout")) { if (!getLoginEvents) { continue; // we don't want to show login/logout activity here } } else if (getLoginEvents) { continue; // we don't want to show non-login/logout activity here } } if (prevTransID != null && !prevTransID.equals(transactionID)) { buffer.append( "---------- End Transaction " + transDate.toString() + ": " + transAdmin + " ----------\n\n"); transAdmin = null; transDate = null; prevTransID = null; } if (token.equals("starttransaction")) { transDate = new Date(time); String tmp2 = "---------- Transaction " + transDate.toString() + ": " + adminName + " ----------\n"; // remember our admin name and transaction id so we // can put out a notice about closing the previous // transaction when we see it transAdmin = adminName; prevTransID = transactionID; buffer.append(tmp2); } else if (token.equals("finishtransaction")) { String tmp2 = "---------- End Transaction " + new Date(time).toString() + ": " + adminName + " ----------\n\n"; prevTransID = null; transAdmin = null; transDate = null; buffer.append(tmp2); } else { String tmp = token + "\n" + WordWrap.wrap(text, 78, "\t") + "\n"; buffer.append(tmp); } } } catch (SQLException ex) { Ganymede.debug("SQLException in retrieveHistory: " + ex.getMessage()); Ganymede.debug("** SQLState: " + ex.getSQLState()); Ganymede.debug("** SQL Error Code: " + ex.getErrorCode()); Ganymede.debug(Ganymede.stackTrace(ex)); return buffer; } finally { try { if (rs != null) { rs.close(); Statement st = rs.getStatement(); if (st != null) { st.close(); } } } catch (SQLException ex) { } } return buffer; }
/** * This method writes the given event to the persistent storage managed by this controller. * * @param event The DBLogEvent to be recorded */ public synchronized void writeEvent(DBLogEvent event) { if (con == null) { throw new IllegalArgumentException("no connection to postgres database"); } try { if (statement == null) { statement = con.prepareStatement( "insert into event (event_id, javatime, sqltime, classtoken, " + "admin_invid, admin_name, trans_id, text) " + "values (?,?,?,?,?,?,?,?)"); } if (event.eventClassToken.equals("starttransaction")) { transactionID = event.transactionID; } primaryKey++; statement.setInt(1, primaryKey); statement.setLong(2, event.time.getTime()); statement.setTimestamp(3, new java.sql.Timestamp(event.time.getTime())); statement.setString(4, event.eventClassToken); if (event.admin != null) { statement.setString(5, event.admin.toString()); } else { statement.setNull(5, java.sql.Types.VARCHAR); } if (event.adminName != null) { statement.setString(6, event.adminName); } else { statement.setNull(5, java.sql.Types.VARCHAR); } if (event.transactionID != null) { statement.setString(7, event.transactionID); } else { statement.setNull(5, java.sql.Types.VARCHAR); } if (event.description != null) { statement.setString(8, event.description); } else { statement.setNull(5, java.sql.Types.VARCHAR); } statement.addBatch(); for (String address : event.getMailTargets()) { statement.addBatch( "insert into email (event_id, address) values(" + primaryKey + ", '" + address + "')"); } for (Invid invid : event.getInvids()) { statement.addBatch( "insert into invids (invid, event_id) values('" + invid.toString() + "'," + primaryKey + ")"); } // if we had successfully logged our entire transaction, // record a mapping in the transactions table from each // invid in the transaction (which are recorded in the // starttransaction line) to the transaction id // // if we get a transactionID mismatch we won't record anything // // we reset the transactionID and transInvids List // pointer in any event if (event.eventClassToken.equals("finishtransaction")) { if (transactionID != null && transactionID.equals(event.transactionID)) { for (Invid invid : event.getInvids()) { statement.addBatch( "insert into transactions (invid, trans_id) values('" + invid.toString() + "','" + transactionID + "')"); } } transactionID = null; } statement.executeBatch(); statement.clearParameters(); } catch (SQLException ex) { Ganymede.debug("SQLException in spinLog: " + ex.getMessage()); Ganymede.debug("** SQLState: " + ex.getSQLState()); Ganymede.debug("** SQL Error Code: " + ex.getErrorCode()); Ganymede.debug(Ganymede.stackTrace(ex)); Ganymede.debug("Event was " + event.toString()); return; } }