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
33package com.jme3.network.rmi;
34
35import com.jme3.network.ClientStateListener.DisconnectInfo;
36import com.jme3.network.*;
37import com.jme3.network.serializing.Serializer;
38import com.jme3.util.IntMap;
39import com.jme3.util.IntMap.Entry;
40import java.io.IOException;
41import java.lang.reflect.InvocationTargetException;
42import java.lang.reflect.Method;
43import java.lang.reflect.Proxy;
44import java.util.ArrayList;
45import java.util.HashMap;
46import java.util.logging.Level;
47import java.util.logging.Logger;
48
49public 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