* 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
This commit is contained in:
parent
5faa1f08fe
commit
3087235def
@ -43,10 +43,13 @@ import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* @author Kirill
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
public class AppletHarness extends Applet {
|
||||
|
||||
@ -70,12 +73,26 @@ public class AppletHarness extends Applet {
|
||||
|
||||
// load app cfg
|
||||
if (appCfg != null){
|
||||
InputStream in = null;
|
||||
try {
|
||||
InputStream in = appCfg.openStream();
|
||||
in = appCfg.openStream();
|
||||
settings.load(in);
|
||||
in.close();
|
||||
} 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();
|
||||
} finally {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,20 +39,20 @@ package com.jme3.network.rmi;
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
class MethodDef {
|
||||
public class MethodDef {
|
||||
|
||||
/**
|
||||
* Method name
|
||||
*/
|
||||
String name;
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Return type
|
||||
*/
|
||||
Class<?> retType;
|
||||
public Class<?> retType;
|
||||
|
||||
/**
|
||||
* Parameter types
|
||||
*/
|
||||
Class<?>[] paramTypes;
|
||||
public Class<?>[] paramTypes;
|
||||
}
|
||||
|
@ -37,33 +37,33 @@ import com.jme3.network.serializing.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Serializable
|
||||
class ObjectDef {
|
||||
public class ObjectDef {
|
||||
|
||||
/**
|
||||
* The object name, can be null if undefined.
|
||||
*/
|
||||
String objectName;
|
||||
public String objectName;
|
||||
|
||||
/**
|
||||
* Object ID
|
||||
*/
|
||||
int objectId;
|
||||
public int objectId;
|
||||
|
||||
/**
|
||||
* Methods of the implementation on the local client. Set to null
|
||||
* on remote clients.
|
||||
*/
|
||||
Method[] methods;
|
||||
public Method[] methods;
|
||||
|
||||
/**
|
||||
* Method definitions of the implementation. Set to null on
|
||||
* the local client.
|
||||
*/
|
||||
MethodDef[] methodDefs;
|
||||
public MethodDef[] methodDefs;
|
||||
|
||||
@Override
|
||||
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.Entry;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ObjectStore.class.getName());
|
||||
|
||||
private static final class Invocation {
|
||||
|
||||
Object retVal;
|
||||
boolean available = false;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Invocation[" + retVal + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private Client client;
|
||||
private Server server;
|
||||
|
||||
// Local object ID counter
|
||||
private short objectIdCounter = 0;
|
||||
private volatile short objectIdCounter = 0;
|
||||
|
||||
// Local invocation ID counter
|
||||
private short invocationIdCounter = 0;
|
||||
private volatile short invocationIdCounter = 0;
|
||||
|
||||
// Invocations waiting ..
|
||||
private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>();
|
||||
@ -120,10 +131,13 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
|
||||
defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) };
|
||||
|
||||
if (client != null)
|
||||
if (client != null) {
|
||||
client.send(defMsg);
|
||||
else
|
||||
logger.log(Level.INFO, "Client: Sending {0}", defMsg);
|
||||
} else {
|
||||
server.broadcast(defMsg);
|
||||
logger.log(Level.INFO, "Server: Sending {0}", defMsg);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
ro.loadMethods(type);
|
||||
return (T) proxy;
|
||||
@ -163,14 +176,17 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
if (needReturn){
|
||||
call.invocationId = invocationIdCounter++;
|
||||
invoke = new Invocation();
|
||||
// Note: could cause threading issues if used from multiple threads
|
||||
pendingInvocations.put(call.invocationId, invoke);
|
||||
}
|
||||
|
||||
try{
|
||||
if (server != null){
|
||||
remoteObj.client.send(call);
|
||||
logger.log(Level.INFO, "Server: Sending {0}", call);
|
||||
}else{
|
||||
client.send(call);
|
||||
logger.log(Level.INFO, "Client: Sending {0}", call);
|
||||
}
|
||||
} catch (IOException ex){
|
||||
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);
|
||||
return invoke.retVal;
|
||||
}else{
|
||||
@ -194,6 +211,9 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
}
|
||||
|
||||
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){
|
||||
RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message;
|
||||
|
||||
@ -212,27 +232,38 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
}else if (message instanceof RemoteMethodCallMessage){
|
||||
RemoteMethodCallMessage call = (RemoteMethodCallMessage) message;
|
||||
LocalObject localObj = localObjects.get(call.objectId);
|
||||
if (localObj == null)
|
||||
return;
|
||||
|
||||
if (call.methodId < 0 || call.methodId >= localObj.methods.length)
|
||||
return;
|
||||
|
||||
Object obj = localObj.theObject;
|
||||
Method method = localObj.methods[call.methodId];
|
||||
Object[] args = call.args;
|
||||
Object ret;
|
||||
Object ret = null;
|
||||
try {
|
||||
ret = method.invoke(obj, args);
|
||||
} catch (Exception ex){
|
||||
throw new RuntimeException(ex);
|
||||
} catch (IllegalAccessException 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){
|
||||
// send return value back
|
||||
RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage();
|
||||
retMsg.invocationID = invocationIdCounter++;
|
||||
retMsg.invocationID = call.invocationId;
|
||||
retMsg.retVal = ret;
|
||||
try {
|
||||
if (server != null){
|
||||
call.getClient().send(retMsg);
|
||||
logger.log(Level.INFO, "Server: Sending {0}", retMsg);
|
||||
} else{
|
||||
client.send(retMsg);
|
||||
logger.log(Level.INFO, "Client: Sending {0}", retMsg);
|
||||
}
|
||||
} catch (IOException ex){
|
||||
ex.printStackTrace();
|
||||
@ -242,7 +273,8 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message;
|
||||
Invocation invoke = pendingInvocations.get(retMsg.invocationID);
|
||||
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){
|
||||
@ -268,8 +300,10 @@ public class ObjectStore implements MessageListener, ConnectionListener {
|
||||
try {
|
||||
if (this.client != null){
|
||||
this.client.send(defMsg);
|
||||
logger.log(Level.INFO, "Client: Sending {0}", defMsg);
|
||||
} else{
|
||||
client.send(defMsg);
|
||||
logger.log(Level.INFO, "Server: Sending {0}", defMsg);
|
||||
}
|
||||
} catch (IOException ex){
|
||||
ex.printStackTrace();
|
||||
|
@ -41,7 +41,7 @@ import com.jme3.network.serializing.Serializable;
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
@Serializable
|
||||
class RemoteMethodCallMessage extends Message {
|
||||
public class RemoteMethodCallMessage extends Message {
|
||||
|
||||
public RemoteMethodCallMessage(){
|
||||
super(true);
|
||||
@ -50,30 +50,30 @@ class RemoteMethodCallMessage extends Message {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
short methodId;
|
||||
public short methodId;
|
||||
|
||||
/**
|
||||
* Invocation ID is used to identify a particular call if the calling
|
||||
* client needs the return value of the called RMI method.
|
||||
* This is set to zero if the method does not return a value.
|
||||
*/
|
||||
short invocationId;
|
||||
public short invocationId;
|
||||
|
||||
/**
|
||||
* Arguments of the remote method invocation.
|
||||
*/
|
||||
Object[] args;
|
||||
public Object[] args;
|
||||
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MethodCall[objectID=").append(objectId).append(", methodID=")
|
||||
sb.append("RemoteMethodCallMessage[objectID=").append(objectId).append(", methodID=")
|
||||
.append(methodId);
|
||||
if (args != null && args.length > 0){
|
||||
sb.append(", args={");
|
||||
|
@ -43,7 +43,7 @@ import com.jme3.network.serializing.Serializable;
|
||||
* @author Kirill Vainer.
|
||||
*/
|
||||
@Serializable
|
||||
class RemoteMethodReturnMessage extends Message {
|
||||
public class RemoteMethodReturnMessage extends Message {
|
||||
|
||||
public RemoteMethodReturnMessage(){
|
||||
super(true);
|
||||
@ -52,16 +52,16 @@ class RemoteMethodReturnMessage extends Message {
|
||||
/**
|
||||
* Invocation ID that was set in the {@link RemoteMethodCallMessage}.
|
||||
*/
|
||||
short invocationID;
|
||||
public short invocationID;
|
||||
|
||||
/**
|
||||
* The return value, could be null.
|
||||
*/
|
||||
Object retVal;
|
||||
public Object retVal;
|
||||
|
||||
|
||||
@Override
|
||||
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
|
||||
*/
|
||||
@Serializable
|
||||
class RemoteObjectDefMessage extends Message {
|
||||
public class RemoteObjectDefMessage extends Message {
|
||||
|
||||
ObjectDef[] objects;
|
||||
public ObjectDef[] objects;
|
||||
|
||||
public RemoteObjectDefMessage(){
|
||||
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.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* {@link RmiSerializer} is responsible for serializing RMI messages
|
||||
@ -46,12 +48,17 @@ import java.nio.ByteBuffer;
|
||||
*/
|
||||
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 void writeString(ByteBuffer buffer, String string) throws IOException{
|
||||
int length = string.length();
|
||||
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 );
|
||||
@ -73,9 +80,10 @@ public class RmiSerializer extends Serializer {
|
||||
buffer.putShort((short)0);
|
||||
} else {
|
||||
SerializerRegistration reg = Serializer.getSerializerRegistration(clazz);
|
||||
if (reg == null)
|
||||
throw new IOException("Unknown class: "+clazz);
|
||||
|
||||
if (reg == null){
|
||||
logger.log(Level.WARNING, "Unknown class: {0}", clazz);
|
||||
throw new IOException(); // prevents message from being serialized
|
||||
}
|
||||
buffer.putShort(reg.getId());
|
||||
}
|
||||
}
|
||||
@ -85,10 +93,12 @@ public class RmiSerializer extends Serializer {
|
||||
if (reg == null){
|
||||
// either "void" or unknown val
|
||||
short id = buffer.getShort(buffer.position()-2);
|
||||
if (id == 0)
|
||||
if (id == 0){
|
||||
return void.class;
|
||||
else
|
||||
throw new IOException("Undefined class ID: " + id);
|
||||
} else{
|
||||
logger.log(Level.WARNING, "Undefined class ID: {0}", id);
|
||||
throw new IOException(); // prevents message from being serialized
|
||||
}
|
||||
}
|
||||
return reg.getType();
|
||||
}
|
||||
@ -173,6 +183,10 @@ public class RmiSerializer extends Serializer {
|
||||
buffer.put((byte)0);
|
||||
}else{
|
||||
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){
|
||||
if (obj != null){
|
||||
buffer.put((byte)0x01);
|
||||
|
Loading…
x
Reference in New Issue
Block a user