Home | History | Annotate | Download | only in rmi
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.network.rmi;
     34 
     35 import com.jme3.network.ClientStateListener.DisconnectInfo;
     36 import com.jme3.network.*;
     37 import com.jme3.network.serializing.Serializer;
     38 import com.jme3.util.IntMap;
     39 import com.jme3.util.IntMap.Entry;
     40 import java.io.IOException;
     41 import java.lang.reflect.InvocationTargetException;
     42 import java.lang.reflect.Method;
     43 import java.lang.reflect.Proxy;
     44 import java.util.ArrayList;
     45 import java.util.HashMap;
     46 import java.util.logging.Level;
     47 import java.util.logging.Logger;
     48 
     49 public class ObjectStore {
     50 
     51     private static final Logger logger = Logger.getLogger(ObjectStore.class.getName());
     52 
     53     private static final class Invocation {
     54 
     55         Object retVal;
     56         boolean available = false;
     57 
     58         @Override
     59         public String toString(){
     60             return "Invocation[" + retVal + "]";
     61         }
     62     }
     63 
     64     private Client client;
     65     private Server server;
     66 
     67     private ClientEventHandler clientEventHandler = new ClientEventHandler();
     68     private ServerEventHandler serverEventHandler = new ServerEventHandler();
     69 
     70     // Local object ID counter
     71     private volatile short objectIdCounter = 0;
     72 
     73     // Local invocation ID counter
     74     private volatile short invocationIdCounter = 0;
     75 
     76     // Invocations waiting ..
     77     private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>();
     78 
     79     // Objects I share with other people
     80     private IntMap<LocalObject> localObjects = new IntMap<LocalObject>();
     81 
     82     // Objects others share with me
     83     private HashMap<String, RemoteObject> remoteObjects = new HashMap<String, RemoteObject>();
     84     private IntMap<RemoteObject> remoteObjectsById = new IntMap<RemoteObject>();
     85 
     86     private final Object receiveObjectLock = new Object();
     87 
     88     public class ServerEventHandler implements MessageListener<HostedConnection>,
     89                                                       ConnectionListener {
     90 
     91         public void messageReceived(HostedConnection source, Message m) {
     92             onMessage(source, m);
     93         }
     94 
     95         public void connectionAdded(Server server, HostedConnection conn) {
     96             onConnection(conn);
     97         }
     98 
     99         public void connectionRemoved(Server server, HostedConnection conn) {
    100         }
    101 
    102     }
    103 
    104     public class ClientEventHandler implements MessageListener,
    105                                                       ClientStateListener {
    106 
    107         public void messageReceived(Object source, Message m) {
    108             onMessage(null, m);
    109         }
    110 
    111         public void clientConnected(Client c) {
    112             onConnection(null);
    113         }
    114 
    115         public void clientDisconnected(Client c, DisconnectInfo info) {
    116         }
    117 
    118     }
    119 
    120     static {
    121         Serializer s = new RmiSerializer();
    122         Serializer.registerClass(RemoteObjectDefMessage.class, s);
    123         Serializer.registerClass(RemoteMethodCallMessage.class, s);
    124         Serializer.registerClass(RemoteMethodReturnMessage.class, s);
    125     }
    126 
    127     public ObjectStore(Client client) {
    128         this.client = client;
    129         client.addMessageListener(clientEventHandler,
    130                 RemoteObjectDefMessage.class,
    131                 RemoteMethodCallMessage.class,
    132                 RemoteMethodReturnMessage.class);
    133         client.addClientStateListener(clientEventHandler);
    134     }
    135 
    136     public ObjectStore(Server server) {
    137         this.server = server;
    138         server.addMessageListener(serverEventHandler,
    139                 RemoteObjectDefMessage.class,
    140                 RemoteMethodCallMessage.class,
    141                 RemoteMethodReturnMessage.class);
    142         server.addConnectionListener(serverEventHandler);
    143     }
    144 
    145     private ObjectDef makeObjectDef(LocalObject localObj){
    146         ObjectDef def = new ObjectDef();
    147         def.objectName = localObj.objectName;
    148         def.objectId   = localObj.objectId;
    149         def.methods    = localObj.methods;
    150         return def;
    151     }
    152 
    153     public void exposeObject(String name, Object obj) throws IOException{
    154         // Create a local object
    155         LocalObject localObj = new LocalObject();
    156         localObj.objectName = name;
    157         localObj.objectId  = objectIdCounter++;
    158         localObj.theObject = obj;
    159         //localObj.methods   = obj.getClass().getMethods();
    160 
    161         ArrayList<Method> methodList = new ArrayList<Method>();
    162         for (Method method : obj.getClass().getMethods()){
    163             if (method.getDeclaringClass() == obj.getClass()){
    164                 methodList.add(method);
    165             }
    166         }
    167         localObj.methods = methodList.toArray(new Method[methodList.size()]);
    168 
    169         // Put it in the store
    170         localObjects.put(localObj.objectId, localObj);
    171 
    172         // Inform the others of its existence
    173         RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
    174         defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) };
    175 
    176         if (client != null) {
    177             client.send(defMsg);
    178             logger.log(Level.INFO, "Client: Sending {0}", defMsg);
    179         } else {
    180             server.broadcast(defMsg);
    181             logger.log(Level.INFO, "Server: Sending {0}", defMsg);
    182         }
    183     }
    184 
    185     public <T> T getExposedObject(String name, Class<T> type, boolean waitFor) throws InterruptedException{
    186         RemoteObject ro = remoteObjects.get(name);
    187         if (ro == null){
    188             if (!waitFor)
    189                 throw new RuntimeException("Cannot find remote object named: " + name);
    190             else{
    191                 do {
    192                     synchronized (receiveObjectLock){
    193                         receiveObjectLock.wait();
    194                     }
    195                 } while ( (ro = remoteObjects.get(name)) == null );
    196             }
    197         }
    198 
    199         Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro);
    200         ro.loadMethods(type);
    201         return (T) proxy;
    202     }
    203 
    204     Object invokeRemoteMethod(RemoteObject remoteObj, Method method, Object[] args){
    205         Integer methodIdInt = remoteObj.methodMap.get(method);
    206         if (methodIdInt == null)
    207              throw new RuntimeException("Method not implemented by remote object owner: "+method);
    208 
    209         boolean needReturn = method.getReturnType() != void.class;
    210         short objectId = remoteObj.objectId;
    211         short methodId = methodIdInt.shortValue();
    212         RemoteMethodCallMessage call = new RemoteMethodCallMessage();
    213         call.methodId = methodId;
    214         call.objectId = objectId;
    215         call.args = args;
    216 
    217         Invocation invoke = null;
    218         if (needReturn){
    219             call.invocationId = invocationIdCounter++;
    220             invoke = new Invocation();
    221             // Note: could cause threading issues if used from multiple threads
    222             pendingInvocations.put(call.invocationId, invoke);
    223         }
    224 
    225         if (server != null){
    226             remoteObj.client.send(call);
    227             logger.log(Level.INFO, "Server: Sending {0}", call);
    228         }else{
    229             client.send(call);
    230             logger.log(Level.INFO, "Client: Sending {0}", call);
    231         }
    232 
    233         if (invoke != null){
    234             synchronized(invoke){
    235                 while (!invoke.available){
    236                     try {
    237                         invoke.wait();
    238                     } catch (InterruptedException ex){
    239                         ex.printStackTrace();
    240                     }
    241                 }
    242             }
    243             // Note: could cause threading issues if used from multiple threads
    244             pendingInvocations.remove(call.invocationId);
    245             return invoke.retVal;
    246         }else{
    247             return null;
    248         }
    249     }
    250 
    251     private void onMessage(HostedConnection source, Message message) {
    252         // Might want to do more strict validation of the data
    253         // in the message to prevent crashes
    254 
    255         if (message instanceof RemoteObjectDefMessage){
    256             RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message;
    257 
    258             ObjectDef[] defs = defMsg.objects;
    259             for (ObjectDef def : defs){
    260                 RemoteObject remoteObject = new RemoteObject(this, source);
    261                 remoteObject.objectId = (short)def.objectId;
    262                 remoteObject.methodDefs = def.methodDefs;
    263                 remoteObjects.put(def.objectName, remoteObject);
    264                 remoteObjectsById.put(def.objectId, remoteObject);
    265             }
    266 
    267             synchronized (receiveObjectLock){
    268                 receiveObjectLock.notifyAll();
    269             }
    270         }else if (message instanceof RemoteMethodCallMessage){
    271             RemoteMethodCallMessage call = (RemoteMethodCallMessage) message;
    272             LocalObject localObj = localObjects.get(call.objectId);
    273             if (localObj == null)
    274                 return;
    275 
    276             if (call.methodId < 0 || call.methodId >= localObj.methods.length)
    277                 return;
    278 
    279             Object obj = localObj.theObject;
    280             Method method = localObj.methods[call.methodId];
    281             Object[] args = call.args;
    282             Object ret = null;
    283             try {
    284                 ret = method.invoke(obj, args);
    285             } catch (IllegalAccessException ex){
    286                 logger.log(Level.WARNING, "RMI: Error accessing method", ex);
    287             } catch (IllegalArgumentException ex){
    288                 logger.log(Level.WARNING, "RMI: Invalid arguments", ex);
    289             } catch (InvocationTargetException ex){
    290                 logger.log(Level.WARNING, "RMI: Invocation exception", ex);
    291             }
    292 
    293             if (method.getReturnType() != void.class){
    294                 // send return value back
    295                 RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage();
    296                 retMsg.invocationID = call.invocationId;
    297                 retMsg.retVal = ret;
    298                 if (server != null){
    299                     source.send(retMsg);
    300                     logger.log(Level.INFO, "Server: Sending {0}", retMsg);
    301                 } else{
    302                     client.send(retMsg);
    303                     logger.log(Level.INFO, "Client: Sending {0}", retMsg);
    304                 }
    305             }
    306         }else if (message instanceof RemoteMethodReturnMessage){
    307             RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message;
    308             Invocation invoke = pendingInvocations.get(retMsg.invocationID);
    309             if (invoke == null){
    310                 logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID);
    311                 return;
    312             }
    313 
    314             synchronized (invoke){
    315                 invoke.retVal = retMsg.retVal;
    316                 invoke.available = true;
    317                 invoke.notifyAll();
    318             }
    319         }
    320     }
    321 
    322     private void onConnection(HostedConnection conn) {
    323         if (localObjects.size() > 0){
    324             // send a object definition message
    325             ObjectDef[] defs = new ObjectDef[localObjects.size()];
    326             int i = 0;
    327             for (Entry<LocalObject> entry : localObjects){
    328                 defs[i] = makeObjectDef(entry.getValue());
    329                 i++;
    330             }
    331 
    332             RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage();
    333             defMsg.objects = defs;
    334             if (this.client != null){
    335                 this.client.send(defMsg);
    336                 logger.log(Level.INFO, "Client: Sending {0}", defMsg);
    337             } else{
    338                 conn.send(defMsg);
    339                 logger.log(Level.INFO, "Server: Sending {0}", defMsg);
    340             }
    341         }
    342     }
    343 
    344 }
    345