/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.network.rmi; import com.jme3.network.*; import com.jme3.network.ClientStateListener.DisconnectInfo; 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.ArrayList; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; public class ObjectStore { 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; private ClientEventHandler clientEventHandler = new ClientEventHandler(); private ServerEventHandler serverEventHandler = new ServerEventHandler(); // Local object ID counter private volatile short objectIdCounter = 0; // Local invocation ID counter private volatile short invocationIdCounter = 0; // Invocations waiting .. private IntMap<Invocation> pendingInvocations = new IntMap<Invocation>(); // Objects I share with other people private IntMap<LocalObject> localObjects = new IntMap<LocalObject>(); // Objects others share with me private HashMap<String, RemoteObject> remoteObjects = new HashMap<String, RemoteObject>(); private IntMap<RemoteObject> remoteObjectsById = new IntMap<RemoteObject>(); private final Object receiveObjectLock = new Object(); public class ServerEventHandler implements MessageListener<HostedConnection>, ConnectionListener { public void messageReceived(HostedConnection source, Message m) { onMessage(source, m); } public void connectionAdded(Server server, HostedConnection conn) { onConnection(conn); } public void connectionRemoved(Server server, HostedConnection conn) { } } public class ClientEventHandler implements MessageListener, ClientStateListener { public void messageReceived(Object source, Message m) { onMessage(null, m); } public void clientConnected(Client c) { onConnection(null); } public void clientDisconnected(Client c, DisconnectInfo info) { } } static { Serializer s = new RmiSerializer(); Serializer.registerClass(RemoteObjectDefMessage.class, s); Serializer.registerClass(RemoteMethodCallMessage.class, s); Serializer.registerClass(RemoteMethodReturnMessage.class, s); } public ObjectStore(Client client) { this.client = client; client.addMessageListener(clientEventHandler, RemoteObjectDefMessage.class, RemoteMethodCallMessage.class, RemoteMethodReturnMessage.class); client.addClientStateListener(clientEventHandler); } public ObjectStore(Server server) { this.server = server; server.addMessageListener(serverEventHandler, RemoteObjectDefMessage.class, RemoteMethodCallMessage.class, RemoteMethodReturnMessage.class); server.addConnectionListener(serverEventHandler); } private ObjectDef makeObjectDef(LocalObject localObj){ ObjectDef def = new ObjectDef(); def.objectName = localObj.objectName; def.objectId = localObj.objectId; def.methods = localObj.methods; return def; } public void exposeObject(String name, Object obj) throws IOException{ // Create a local object LocalObject localObj = new LocalObject(); localObj.objectName = name; localObj.objectId = objectIdCounter++; localObj.theObject = obj; //localObj.methods = obj.getClass().getMethods(); ArrayList<Method> methodList = new ArrayList<Method>(); for (Method method : obj.getClass().getMethods()){ if (method.getDeclaringClass() == obj.getClass()){ methodList.add(method); } } localObj.methods = methodList.toArray(new Method[methodList.size()]); // Put it in the store localObjects.put(localObj.objectId, localObj); // Inform the others of its existence RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) }; if (client != null) { client.send(defMsg); logger.log(Level.FINE, "Client: Sending {0}", defMsg); } else { server.broadcast(defMsg); logger.log(Level.FINE, "Server: Sending {0}", defMsg); } } public <T> T getExposedObject(String name, Class<T> type, boolean waitFor) throws InterruptedException{ RemoteObject ro = remoteObjects.get(name); if (ro == null){ if (!waitFor) throw new RuntimeException("Cannot find remote object named: " + name); else{ do { synchronized (receiveObjectLock){ receiveObjectLock.wait(); } } while ( (ro = remoteObjects.get(name)) == null ); } } Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro); ro.loadMethods(type); return (T) proxy; } Object invokeRemoteMethod(RemoteObject remoteObj, Method method, Object[] args){ Integer methodIdInt = remoteObj.methodMap.get(method); if (methodIdInt == null) throw new RuntimeException("Method not implemented by remote object owner: "+method); boolean needReturn = method.getReturnType() != void.class; short objectId = remoteObj.objectId; short methodId = methodIdInt.shortValue(); RemoteMethodCallMessage call = new RemoteMethodCallMessage(); call.methodId = methodId; call.objectId = objectId; call.args = args; Invocation invoke = null; if (needReturn){ call.invocationId = invocationIdCounter++; invoke = new Invocation(); // Note: could cause threading issues if used from multiple threads pendingInvocations.put(call.invocationId, invoke); } if (server != null){ remoteObj.client.send(call); logger.log(Level.FINE, "Server: Sending {0}", call); }else{ client.send(call); logger.log(Level.FINE, "Client: Sending {0}", call); } if (invoke != null){ synchronized(invoke){ while (!invoke.available){ try { invoke.wait(); } catch (InterruptedException ex){ ex.printStackTrace(); } } } // Note: could cause threading issues if used from multiple threads pendingInvocations.remove(call.invocationId); return invoke.retVal; }else{ return null; } } private void onMessage(HostedConnection source, 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; ObjectDef[] defs = defMsg.objects; for (ObjectDef def : defs){ RemoteObject remoteObject = new RemoteObject(this, source); remoteObject.objectId = (short)def.objectId; remoteObject.methodDefs = def.methodDefs; remoteObjects.put(def.objectName, remoteObject); remoteObjectsById.put(def.objectId, remoteObject); } synchronized (receiveObjectLock){ receiveObjectLock.notifyAll(); } }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 = null; try { ret = method.invoke(obj, args); } 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 = call.invocationId; retMsg.retVal = ret; if (server != null){ source.send(retMsg); logger.log(Level.FINE, "Server: Sending {0}", retMsg); } else{ client.send(retMsg); logger.log(Level.FINE, "Client: Sending {0}", retMsg); } } }else if (message instanceof RemoteMethodReturnMessage){ RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message; Invocation invoke = pendingInvocations.get(retMsg.invocationID); if (invoke == null){ logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID); return; } synchronized (invoke){ invoke.retVal = retMsg.retVal; invoke.available = true; invoke.notifyAll(); } } } private void onConnection(HostedConnection conn) { if (localObjects.size() > 0){ // send a object definition message ObjectDef[] defs = new ObjectDef[localObjects.size()]; int i = 0; for (Entry<LocalObject> entry : localObjects){ defs[i] = makeObjectDef(entry.getValue()); i++; } RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); defMsg.objects = defs; if (this.client != null){ this.client.send(defMsg); logger.log(Level.FINE, "Client: Sending {0}", defMsg); } else{ conn.send(defMsg); logger.log(Level.FINE, "Server: Sending {0}", defMsg); } } } }