blob: 137320ddb14e4f065c0bcf1170a88924a08f5e39 [file] [log] [blame]
/*
* Copyright (c) 2009-2010 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.ClientStateListener.DisconnectInfo;
import com.jme3.network.*;
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.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{
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.INFO, "Server: Sending {0}", call);
}else{
client.send(call);
logger.log(Level.INFO, "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.INFO, "Server: Sending {0}", retMsg);
} else{
client.send(retMsg);
logger.log(Level.INFO, "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.INFO, "Client: Sending {0}", defMsg);
} else{
conn.send(defMsg);
logger.log(Level.INFO, "Server: Sending {0}", defMsg);
}
}
}
}