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