* RMI system much more tolerant of bad data now (will display warning in log instead of crashing)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7040 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
sha..rd 14 years ago
parent 5faa1f08fe
commit 3087235def
  1. 21
      engine/src/desktop/com/jme3/app/AppletHarness.java
  2. 8
      engine/src/networking/com/jme3/network/rmi/MethodDef.java
  3. 12
      engine/src/networking/com/jme3/network/rmi/ObjectDef.java
  4. 54
      engine/src/networking/com/jme3/network/rmi/ObjectStore.java
  5. 12
      engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java
  6. 8
      engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java
  7. 15
      engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java
  8. 28
      engine/src/networking/com/jme3/network/rmi/RmiSerializer.java

@ -43,10 +43,13 @@ import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
/** /**
* @author Kirill * @author Kirill Vainer
*/ */
public class AppletHarness extends Applet { public class AppletHarness extends Applet {
@ -70,12 +73,26 @@ public class AppletHarness extends Applet {
// load app cfg // load app cfg
if (appCfg != null){ if (appCfg != null){
InputStream in = null;
try { try {
InputStream in = appCfg.openStream(); in = appCfg.openStream();
settings.load(in); settings.load(in);
in.close(); in.close();
} catch (IOException ex){ } catch (IOException ex){
// Called before application has been created ....
// Display error message through AWT
JOptionPane.showMessageDialog(this, "An error has occured while "
+ "loading applet configuration"
+ ex.getMessage(),
"jME3 Applet",
JOptionPane.ERROR_MESSAGE);
ex.printStackTrace(); ex.printStackTrace();
} finally {
if (in != null)
try {
in.close();
} catch (IOException ex) {
}
} }
} }

@ -39,20 +39,20 @@ package com.jme3.network.rmi;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
class MethodDef { public class MethodDef {
/** /**
* Method name * Method name
*/ */
String name; public String name;
/** /**
* Return type * Return type
*/ */
Class<?> retType; public Class<?> retType;
/** /**
* Parameter types * Parameter types
*/ */
Class<?>[] paramTypes; public Class<?>[] paramTypes;
} }

@ -37,33 +37,33 @@ import com.jme3.network.serializing.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@Serializable @Serializable
class ObjectDef { public class ObjectDef {
/** /**
* The object name, can be null if undefined. * The object name, can be null if undefined.
*/ */
String objectName; public String objectName;
/** /**
* Object ID * Object ID
*/ */
int objectId; public int objectId;
/** /**
* Methods of the implementation on the local client. Set to null * Methods of the implementation on the local client. Set to null
* on remote clients. * on remote clients.
*/ */
Method[] methods; public Method[] methods;
/** /**
* Method definitions of the implementation. Set to null on * Method definitions of the implementation. Set to null on
* the local client. * the local client.
*/ */
MethodDef[] methodDefs; public MethodDef[] methodDefs;
@Override @Override
public String toString(){ public String toString(){
return "ObjectDef[name=" + objectName + ", ID=" + objectId+"]"; return "ObjectDef[name=" + objectName + ", objectId=" + objectId+"]";
} }
} }

@ -42,25 +42,36 @@ import com.jme3.network.serializing.Serializer;
import com.jme3.util.IntMap; import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry; import com.jme3.util.IntMap.Entry;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.HashMap; import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ObjectStore implements MessageListener, ConnectionListener { public class ObjectStore implements MessageListener, ConnectionListener {
private static final Logger logger = Logger.getLogger(ObjectStore.class.getName());
private static final class Invocation { private static final class Invocation {
Object retVal; Object retVal;
boolean available = false; boolean available = false;
@Override
public String toString(){
return "Invocation[" + retVal + "]";
}
} }
private Client client; private Client client;
private Server server; private Server server;
// Local object ID counter // Local object ID counter
private short objectIdCounter = 0; private volatile short objectIdCounter = 0;
// Local invocation ID counter // Local invocation ID counter
private short invocationIdCounter = 0; private volatile short invocationIdCounter = 0;
// Invocations waiting .. // Invocations waiting ..
private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>(); private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>();
@ -120,10 +131,13 @@ public class ObjectStore implements MessageListener, ConnectionListener {
RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) }; defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) };
if (client != null) if (client != null) {
client.send(defMsg); client.send(defMsg);
else logger.log(Level.INFO, "Client: Sending {0}", defMsg);
} else {
server.broadcast(defMsg); server.broadcast(defMsg);
logger.log(Level.INFO, "Server: Sending {0}", defMsg);
}
} }
public <T> T getExposedObject(String name, Class<T> type, boolean waitFor) throws InterruptedException{ public <T> T getExposedObject(String name, Class<T> type, boolean waitFor) throws InterruptedException{
@ -140,7 +154,6 @@ public class ObjectStore implements MessageListener, ConnectionListener {
} }
} }
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro); Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro);
ro.loadMethods(type); ro.loadMethods(type);
return (T) proxy; return (T) proxy;
@ -163,14 +176,17 @@ public class ObjectStore implements MessageListener, ConnectionListener {
if (needReturn){ if (needReturn){
call.invocationId = invocationIdCounter++; call.invocationId = invocationIdCounter++;
invoke = new Invocation(); invoke = new Invocation();
// Note: could cause threading issues if used from multiple threads
pendingInvocations.put(call.invocationId, invoke); pendingInvocations.put(call.invocationId, invoke);
} }
try{ try{
if (server != null){ if (server != null){
remoteObj.client.send(call); remoteObj.client.send(call);
logger.log(Level.INFO, "Server: Sending {0}", call);
}else{ }else{
client.send(call); client.send(call);
logger.log(Level.INFO, "Client: Sending {0}", call);
} }
} catch (IOException ex){ } catch (IOException ex){
ex.printStackTrace(); ex.printStackTrace();
@ -186,6 +202,7 @@ public class ObjectStore implements MessageListener, ConnectionListener {
} }
} }
} }
// Note: could cause threading issues if used from multiple threads
pendingInvocations.remove(call.invocationId); pendingInvocations.remove(call.invocationId);
return invoke.retVal; return invoke.retVal;
}else{ }else{
@ -194,6 +211,9 @@ public class ObjectStore implements MessageListener, ConnectionListener {
} }
public void messageReceived(Message message) { public void messageReceived(Message message) {
// Might want to do more strict validation of the data
// in the message to prevent crashes
if (message instanceof RemoteObjectDefMessage){ if (message instanceof RemoteObjectDefMessage){
RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message; RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message;
@ -212,27 +232,38 @@ public class ObjectStore implements MessageListener, ConnectionListener {
}else if (message instanceof RemoteMethodCallMessage){ }else if (message instanceof RemoteMethodCallMessage){
RemoteMethodCallMessage call = (RemoteMethodCallMessage) message; RemoteMethodCallMessage call = (RemoteMethodCallMessage) message;
LocalObject localObj = localObjects.get(call.objectId); LocalObject localObj = localObjects.get(call.objectId);
if (localObj == null)
return;
if (call.methodId < 0 || call.methodId >= localObj.methods.length)
return;
Object obj = localObj.theObject; Object obj = localObj.theObject;
Method method = localObj.methods[call.methodId]; Method method = localObj.methods[call.methodId];
Object[] args = call.args; Object[] args = call.args;
Object ret; Object ret = null;
try { try {
ret = method.invoke(obj, args); ret = method.invoke(obj, args);
} catch (Exception ex){ } catch (IllegalAccessException ex){
throw new RuntimeException(ex); logger.log(Level.WARNING, "RMI: Error accessing method", ex);
} catch (IllegalArgumentException ex){
logger.log(Level.WARNING, "RMI: Invalid arguments", ex);
} catch (InvocationTargetException ex){
logger.log(Level.WARNING, "RMI: Invocation exception", ex);
} }
if (method.getReturnType() != void.class){ if (method.getReturnType() != void.class){
// send return value back // send return value back
RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage(); RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage();
retMsg.invocationID = invocationIdCounter++; retMsg.invocationID = call.invocationId;
retMsg.retVal = ret; retMsg.retVal = ret;
try { try {
if (server != null){ if (server != null){
call.getClient().send(retMsg); call.getClient().send(retMsg);
logger.log(Level.INFO, "Server: Sending {0}", retMsg);
} else{ } else{
client.send(retMsg); client.send(retMsg);
logger.log(Level.INFO, "Client: Sending {0}", retMsg);
} }
} catch (IOException ex){ } catch (IOException ex){
ex.printStackTrace(); ex.printStackTrace();
@ -242,7 +273,8 @@ public class ObjectStore implements MessageListener, ConnectionListener {
RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message; RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message;
Invocation invoke = pendingInvocations.get(retMsg.invocationID); Invocation invoke = pendingInvocations.get(retMsg.invocationID);
if (invoke == null){ if (invoke == null){
throw new RuntimeException("Cannot find invocation ID: " + retMsg.invocationID); logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID);
return;
} }
synchronized (invoke){ synchronized (invoke){
@ -268,8 +300,10 @@ public class ObjectStore implements MessageListener, ConnectionListener {
try { try {
if (this.client != null){ if (this.client != null){
this.client.send(defMsg); this.client.send(defMsg);
logger.log(Level.INFO, "Client: Sending {0}", defMsg);
} else{ } else{
client.send(defMsg); client.send(defMsg);
logger.log(Level.INFO, "Server: Sending {0}", defMsg);
} }
} catch (IOException ex){ } catch (IOException ex){
ex.printStackTrace(); ex.printStackTrace();

@ -41,7 +41,7 @@ import com.jme3.network.serializing.Serializable;
* @author Kirill Vainer * @author Kirill Vainer
*/ */
@Serializable @Serializable
class RemoteMethodCallMessage extends Message { public class RemoteMethodCallMessage extends Message {
public RemoteMethodCallMessage(){ public RemoteMethodCallMessage(){
super(true); super(true);
@ -50,30 +50,30 @@ class RemoteMethodCallMessage extends Message {
/** /**
* The object ID on which the call is being made. * The object ID on which the call is being made.
*/ */
int objectId; public int objectId;
/** /**
* The method ID used for look-up in the LocalObject.methods array. * The method ID used for look-up in the LocalObject.methods array.
*/ */
short methodId; public short methodId;
/** /**
* Invocation ID is used to identify a particular call if the calling * Invocation ID is used to identify a particular call if the calling
* client needs the return value of the called RMI method. * client needs the return value of the called RMI method.
* This is set to zero if the method does not return a value. * This is set to zero if the method does not return a value.
*/ */
short invocationId; public short invocationId;
/** /**
* Arguments of the remote method invocation. * Arguments of the remote method invocation.
*/ */
Object[] args; public Object[] args;
@Override @Override
public String toString(){ public String toString(){
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("MethodCall[objectID=").append(objectId).append(", methodID=") sb.append("RemoteMethodCallMessage[objectID=").append(objectId).append(", methodID=")
.append(methodId); .append(methodId);
if (args != null && args.length > 0){ if (args != null && args.length > 0){
sb.append(", args={"); sb.append(", args={");

@ -43,7 +43,7 @@ import com.jme3.network.serializing.Serializable;
* @author Kirill Vainer. * @author Kirill Vainer.
*/ */
@Serializable @Serializable
class RemoteMethodReturnMessage extends Message { public class RemoteMethodReturnMessage extends Message {
public RemoteMethodReturnMessage(){ public RemoteMethodReturnMessage(){
super(true); super(true);
@ -52,16 +52,16 @@ class RemoteMethodReturnMessage extends Message {
/** /**
* Invocation ID that was set in the {@link RemoteMethodCallMessage}. * Invocation ID that was set in the {@link RemoteMethodCallMessage}.
*/ */
short invocationID; public short invocationID;
/** /**
* The return value, could be null. * The return value, could be null.
*/ */
Object retVal; public Object retVal;
@Override @Override
public String toString(){ public String toString(){
return "MethodReturn[ID="+invocationID+", Value="+retVal.toString()+"]"; return "RemoteMethodReturnMessage[ID="+invocationID+", Value="+retVal.toString()+"]";
} }
} }

@ -42,12 +42,23 @@ import com.jme3.network.serializing.Serializable;
* @author Kirill Vainer * @author Kirill Vainer
*/ */
@Serializable @Serializable
class RemoteObjectDefMessage extends Message { public class RemoteObjectDefMessage extends Message {
ObjectDef[] objects; public ObjectDef[] objects;
public RemoteObjectDefMessage(){ public RemoteObjectDefMessage(){
super(true); super(true);
} }
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("RemoteObjectDefMessage[\n");
for (ObjectDef def : objects){
sb.append("\t").append(def).append("\n");
}
sb.append("]");
return sb.toString();
}
} }

@ -37,6 +37,8 @@ import com.jme3.network.serializing.SerializerRegistration;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* {@link RmiSerializer} is responsible for serializing RMI messages * {@link RmiSerializer} is responsible for serializing RMI messages
@ -46,12 +48,17 @@ import java.nio.ByteBuffer;
*/ */
public class RmiSerializer extends Serializer { public class RmiSerializer extends Serializer {
private static final Logger logger = Logger.getLogger(RmiSerializer.class.getName());
// not good for multithread applications
private char[] chrBuf = new char[256]; private char[] chrBuf = new char[256];
private void writeString(ByteBuffer buffer, String string) throws IOException{ private void writeString(ByteBuffer buffer, String string) throws IOException{
int length = string.length(); int length = string.length();
if (length > 255){ if (length > 255){
throw new IOException("Cannot serialize: "+ string + "\nToo long!"); logger.log(Level.WARNING, "The string length exceeds the limit! {0} > 255", length);
buffer.put( (byte) 0 );
return;
} }
buffer.put( (byte) length ); buffer.put( (byte) length );
@ -73,9 +80,10 @@ public class RmiSerializer extends Serializer {
buffer.putShort((short)0); buffer.putShort((short)0);
} else { } else {
SerializerRegistration reg = Serializer.getSerializerRegistration(clazz); SerializerRegistration reg = Serializer.getSerializerRegistration(clazz);
if (reg == null) if (reg == null){
throw new IOException("Unknown class: "+clazz); logger.log(Level.WARNING, "Unknown class: {0}", clazz);
throw new IOException(); // prevents message from being serialized
}
buffer.putShort(reg.getId()); buffer.putShort(reg.getId());
} }
} }
@ -85,10 +93,12 @@ public class RmiSerializer extends Serializer {
if (reg == null){ if (reg == null){
// either "void" or unknown val // either "void" or unknown val
short id = buffer.getShort(buffer.position()-2); short id = buffer.getShort(buffer.position()-2);
if (id == 0) if (id == 0){
return void.class; return void.class;
else } else{
throw new IOException("Undefined class ID: " + id); logger.log(Level.WARNING, "Undefined class ID: {0}", id);
throw new IOException(); // prevents message from being serialized
}
} }
return reg.getType(); return reg.getType();
} }
@ -173,6 +183,10 @@ public class RmiSerializer extends Serializer {
buffer.put((byte)0); buffer.put((byte)0);
}else{ }else{
buffer.put((byte)call.args.length); buffer.put((byte)call.args.length);
// Right now it writes 0 for every null argument
// and 1 for every non-null argument followed by the serialized
// argument. For the future, using a bit set should be considered.
for (Object obj : call.args){ for (Object obj : call.args){
if (obj != null){ if (obj != null){
buffer.put((byte)0x01); buffer.put((byte)0x01);

Loading…
Cancel
Save