Kaynağa Gözat

* 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
sha..rd 14 yıl önce
ebeveyn
işleme
3087235def

+ 19 - 2
engine/src/desktop/com/jme3/app/AppletHarness.java

@@ -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) {
+                }
             }
         }
 

+ 4 - 4
engine/src/networking/com/jme3/network/rmi/MethodDef.java

@@ -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;
 }

+ 6 - 6
engine/src/networking/com/jme3/network/rmi/ObjectDef.java

@@ -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+"]";
     }
 
 }

+ 44 - 10
engine/src/networking/com/jme3/network/rmi/ObjectStore.java

@@ -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();

+ 6 - 6
engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java

@@ -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={");

+ 4 - 4
engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java

@@ -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()+"]";
     }
 }

+ 13 - 2
engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java

@@ -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();
+    }
+
 }

+ 21 - 7
engine/src/networking/com/jme3/network/rmi/RmiSerializer.java

@@ -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);