Removed and reworked RMI so that local (in-jvm) connections have the

exact same behavior as network connections. This was to remove
workarounds present when local in-jvm connections were used
This commit is contained in:
nathan 2018-01-22 15:25:36 +01:00
parent d6e7affa1e
commit ffc2375fe8
18 changed files with 660 additions and 577 deletions

View File

@ -1,26 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection;
class ClassObject {
final Class<?> clazz;
final Object object;
public ClassObject(final Class<?> implementationClass, final Object remotePrimaryObject) {
clazz = implementationClass;
object = remotePrimaryObject;
}
}

View File

@ -17,6 +17,7 @@ package dorkbox.network.connection;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.WeakHashMap;
@ -42,7 +43,8 @@ import dorkbox.network.rmi.RemoteObjectCallback;
import dorkbox.network.rmi.Rmi;
import dorkbox.network.rmi.RmiBridge;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.serialization.RmiSerializationManager;
import dorkbox.network.serialization.CryptoSerializationManager;
import dorkbox.util.ClassHelper;
import dorkbox.util.collections.IntMap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
@ -110,7 +112,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
private final RmiBridge rmiBridge;
private final Map<Integer, RemoteObject> proxyIdCache = new WeakHashMap<Integer, RemoteObject>(8);
private final IntMap<RemoteObjectCallback> rmiRegistrationCallbacks = new IntMap<RemoteObjectCallback>();
private int rmiRegistrationID = 0; // protected by synchronized (rmiRegistrationCallbacks)
private int rmiCallbackId = 0; // protected by synchronized (rmiRegistrationCallbacks)
/**
* All of the parameters can be null, when metaChannel wants to get the base class type
@ -902,11 +904,14 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
//
// RMI methods
//
//
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"})
@Override
public final
<Iface> void getRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
<Iface> void createRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("Cannot create a proxy for RMI access. It must be an interface.");
}
@ -914,9 +919,9 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
RmiRegistration message;
synchronized (rmiRegistrationCallbacks) {
int nextRmiID = rmiRegistrationID++;
rmiRegistrationCallbacks.put(nextRmiID, callback);
message = new RmiRegistration(interfaceClass, nextRmiID);
int nextRmiCallbackId = rmiCallbackId++;
rmiRegistrationCallbacks.put(nextRmiCallbackId, callback);
message = new RmiRegistration(interfaceClass, RmiBridge.INVALID_RMI, nextRmiCallbackId);
}
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
@ -932,10 +937,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) {
RmiRegistration message;
Class<?> iFaceClass = ClassHelper.getGenericParameterAsClassForSuperClass(RemoteObjectCallback.class, callback.getClass(), 0);
synchronized (rmiRegistrationCallbacks) {
int nextRmiID = rmiRegistrationID++;
rmiRegistrationCallbacks.put(nextRmiID, callback);
message = new RmiRegistration(objectId, nextRmiID);
int nextRmiCallbackId = rmiCallbackId++;
rmiRegistrationCallbacks.put(nextRmiCallbackId, callback);
message = new RmiRegistration(iFaceClass, objectId, nextRmiCallbackId);
}
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
@ -945,146 +952,129 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
TCP(message).flush();
}
private
void collectRmiFields(final RmiBridge rmiBridge,
final LinkedList<ClassObject> classesToCheck, final ClassObject remoteClassObject, final Field[] fields) {
// default false
public static boolean ENABLE_PROXY_OBJECTS = true;
}
/**
* For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
* For local connections, we have to switch it appropriately in the LocalRmiProxy
*
* @param implementationClass
* @param callbackId
* @return
*/
public final
RmiRegistration createNewRmiObject(final Class<?> interfaceClass, final Class<?> implementationClass, final int callbackId) {
final
void registerInternal(final ConnectionImpl connection, final RmiRegistration remoteRegistration) {
final Class<?> interfaceClass = remoteRegistration.interfaceClass;
final int rmiID = remoteRegistration.rmiID;
CryptoSerializationManager manager = getEndPoint().serializationManager;
if (interfaceClass != null) {
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation)
//
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
KryoExtra kryo = null;
Object object = null;
int rmiId = 0;
// the interface class kryo ID == implementation class kryo ID, so they switcheroo automatically.
final Class<?> implementationClass = interfaceClass;
try {
kryo = manager.takeKryo();
// this is what creates a new instance of the impl class, and stores it as an ID.
object = kryo.newInstance(implementationClass);
final RmiSerializationManager manager = getEndPoint().serializationManager;
KryoExtra kryo = null;
final Object remotePrimaryObject;
try {
kryo = manager.takeKryo();
// this is what creates a new instance of the impl class, and stores it as an ID.
remotePrimaryObject = kryo.newInstance(implementationClass);
} catch (Exception e) {
logger.error("Error creating RMI class " + implementationClass, e);
connection.TCP(new RmiRegistration(rmiID))
.flush();
return;
} finally {
if (kryo != null) {
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
}
}
rmiId = rmiBridge.nextObjectId();
rmiBridge.register(rmiId, object);
try {
rmiBridge.register(rmiBridge.nextObjectId(), remotePrimaryObject);
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI objects
LinkedList<Map.Entry<Class<?>, Object>> classesToCheck = new LinkedList<Map.Entry<Class<?>, Object>>();
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(implementationClass, object));
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI
LinkedList<ClassObject> classesToCheck = new LinkedList<ClassObject>();
classesToCheck.add(new ClassObject(implementationClass, remotePrimaryObject));
Map.Entry<Class<?>, Object> remoteClassObject;
while (!classesToCheck.isEmpty()) {
remoteClassObject = classesToCheck.removeFirst();
ClassObject remoteClassObject;
while (!classesToCheck.isEmpty()) {
remoteClassObject = classesToCheck.removeFirst();
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information.
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible (public + private).
for (Field field : remoteClassObject.getKey()
.getDeclaredFields()) {
if (field.getAnnotation(Rmi.class) != null) {
final Class<?> type = field.getType();
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information.
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible.
for (Field field : remoteClassObject.clazz.getDeclaredFields()) {
if (field.getAnnotation(Rmi.class) != null) {
final Class<?> type = field.getType();
if (!type.isInterface()) {
// the type must be an interface, otherwise RMI cannot create a proxy object
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!", remoteClassObject.clazz, field.getName());
continue;
}
if (!type.isInterface()) {
// the type must be an interface, otherwise RMI cannot create a proxy object
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!",
remoteClassObject.getKey(),
field.getName());
continue;
}
boolean prev = field.isAccessible();
field.setAccessible(true);
final Object o;
try {
o = field.get(remoteClassObject.object);
boolean prev = field.isAccessible();
field.setAccessible(true);
final Object o;
try {
o = field.get(remoteClassObject.getValue());
rmiBridge.register(rmiBridge.nextObjectId(), o);
classesToCheck.add(new ClassObject(type, o));
} catch (IllegalAccessException e) {
logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.clazz, field.getName(), e);
} finally {
field.setAccessible(prev);
}
rmiBridge.register(rmiBridge.nextObjectId(), o);
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(type, o));
} catch (IllegalAccessException e) {
logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
} finally {
field.setAccessible(prev);
}
}
// have to check the object hierarchy as well
Class<?> superclass = remoteClassObject.clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
classesToCheck.add(new ClassObject(superclass, remoteClassObject.object));
}
}
connection.TCP(new RmiRegistration(remotePrimaryObject, rmiID)).flush();
} catch (Exception e) {
logger.error("Error registering RMI class " + implementationClass, e);
connection.TCP(new RmiRegistration(rmiID)).flush();
// have to check the object hierarchy as well
Class<?> superclass = remoteClassObject.getKey()
.getSuperclass();
if (superclass != null && superclass != Object.class) {
classesToCheck.add(new AbstractMap.SimpleEntry<Class<?>, Object>(superclass, remoteClassObject.getValue()));
}
}
} catch (Exception e) {
logger.error("Error registering RMI class " + implementationClass, e);
} finally {
if (kryo != null) {
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
}
}
else if (remoteRegistration.remoteObjectId > RmiBridge.INVALID_RMI) {
// THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist)
//
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
Object object = getImplementationObject(remoteRegistration.remoteObjectId);
if (object != null) {
connection.TCP(new RmiRegistration(object, rmiID)).flush();
} else {
connection.TCP(new RmiRegistration(rmiID)).flush();
}
return new RmiRegistration(interfaceClass, rmiId, callbackId, object);
}
public final
RmiRegistration getExistingRmiObject(final Class<?> interfaceClass, final int rmiId, final int callbackId) {
Object object = getImplementationObject(rmiId);
return new RmiRegistration(interfaceClass, rmiId, callbackId, object);
}
public final
void runRmiCallback(final Class<?> interfaceClass, final int callbackId, final Object remoteObject) {
RemoteObjectCallback callback;
synchronized (rmiRegistrationCallbacks) {
callback = rmiRegistrationCallbacks.remove(callbackId);
}
else {
// THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client.
// this will be null if there was an error
Object remoteObject = remoteRegistration.remoteObject;
boolean noMoreRmiRemaining ;
RemoteObjectCallback callback;
synchronized (rmiRegistrationCallbacks) {
callback = rmiRegistrationCallbacks.remove(remoteRegistration.rmiID);
}
try {
//noinspection unchecked
callback.created(remoteObject);
} catch (Exception e) {
logger.error("Error getting remote object " + remoteObject.getClass() + ", ID: " + rmiID, e);
}
try {
//noinspection unchecked
callback.created(remoteObject);
} catch (Exception e) {
logger.error("Error getting or creating the remote object " + interfaceClass, e);
}
}
/**
* Used by RMI
* Used by RMI by the LOCAL side when setting up the to fetch an object for the REMOTE side
*
* @return the registered ID for a specific object. This is used by the "client" side when setting up the to fetch an object for the
* "service" side for RMI
* @return the registered ID for a specific object.
*/
@Override
public
<T> int getRegisteredId(final T object) {
// always check local before checking global, because less contention on the synchronization
// always check global before checking local, because less contention on the synchronization
RmiBridge globalRmiBridge = endPoint.globalRmiBridge;
if (globalRmiBridge == null) {
@ -1100,7 +1090,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
}
/**
* Used by RMI for the CLIENT side, to get the proxy object as an interface
* Used by RMI for the LOCAL side, to get the proxy object as an interface
*
* @param objectID is the RMI object ID
* @param iFace must be the interface the proxy will bind to

View File

@ -0,0 +1,419 @@
/*
* Copyright 2018 dorkbox, llc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.network.rmi.CachedMethod;
import dorkbox.network.rmi.InvokeMethod;
import dorkbox.network.rmi.RemoteObject;
import dorkbox.network.rmi.RmiBridge;
import dorkbox.network.rmi.RmiMessages;
import dorkbox.network.rmi.RmiProxyHandler;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.serialization.CryptoSerializationManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
/**
* This is for a local-connection (same-JVM) RMI method invocation
*
* Uses the "single writer principle" for fast access, but disregards 'single writer' for field cache, because duplicates are OK
* <p>
* This is for a LOCAL connection (same-JVM)
*/
public
class RegisterRmiLocalHandler extends MessageToMessageDecoder<Object> {
private static final boolean ENABLE_PROXY_OBJECTS = ConnectionImpl.ENABLE_PROXY_OBJECTS;
private static final Field[] NO_REMOTE_FIELDS = new Field[0];
// private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> rmiFieldsREF = AtomicReferenceFieldUpdater.newUpdater(
// RegisterRmiLocalHandler.class,
// IdentityMap.class,
// "fieldCache");
private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> implToProxyREF = AtomicReferenceFieldUpdater.newUpdater(
RegisterRmiLocalHandler.class,
IdentityMap.class,
"implToProxy");
private static final AtomicReferenceFieldUpdater<RegisterRmiLocalHandler, IdentityMap> remoteObjectREF = AtomicReferenceFieldUpdater.newUpdater(
RegisterRmiLocalHandler.class,
IdentityMap.class,
"objectHasRemoteObjects");
// private volatile IdentityMap<Class<?>, Field[]> fieldCache = new IdentityMap<Class<?>, Field[]>();
private volatile IdentityMap<Object, Object> implToProxy = new IdentityMap<Object, Object>();
private volatile IdentityMap<Object, Field[]> objectHasRemoteObjects = new IdentityMap<Object, Field[]>();
public
RegisterRmiLocalHandler() {
}
@Override
protected
void decode(final ChannelHandlerContext context, final Object msg, final List<Object> out) throws Exception {
ConnectionImpl connection = (ConnectionImpl) context.pipeline()
.last();
if (msg instanceof RmiRegistration) {
receivedRegistration(connection, (RmiRegistration) msg);
}
else {
if (msg instanceof InvokeMethod) {
InvokeMethod invokeMethod = (InvokeMethod) msg;
int methodClassID = invokeMethod.cachedMethod.methodClassID;
int methodIndex = invokeMethod.cachedMethod.methodIndex;
// have to replace the cached methods with the correct (remote) version, otherwise the wrong methods CAN BE invoked.
CryptoSerializationManager serialization = connection.getEndPoint()
.getSerialization();
CachedMethod cachedMethod;
try {
cachedMethod = serialization.getMethods(methodClassID)[methodIndex];
} catch (Exception ex) {
String errorMessage;
KryoExtra kryo = null;
try {
kryo = serialization.takeKryo();
Class<?> methodClass = kryo.getRegistration(methodClassID)
.getType();
errorMessage = "Invalid method index " + methodIndex + " for class: " + methodClass.getName();
} finally {
serialization.returnKryo(kryo);
}
throw new KryoException(errorMessage);
}
Object[] args;
Serializer<?>[] serializers = cachedMethod.serializers;
int argStartIndex;
if (cachedMethod.overriddenMethod) {
// did we override our cached method? This is not common.
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
argStartIndex = 1;
args = new Object[serializers.length + 1];
args[0] = connection;
}
else {
argStartIndex = 0;
args = new Object[serializers.length];
}
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
args[j] = invokeMethod.args[i];
}
// overwrite the invoke method fields with UPDATED versions that have the correct (remote side) implementation/args
invokeMethod.cachedMethod = cachedMethod;
invokeMethod.args = args;
}
receivedNormal(connection, msg, out);
}
}
private
void receivedNormal(final ConnectionImpl connection, final Object msg, final List<Object> out) {
// else, this was "just a local message"
if (msg instanceof RmiMessages) {
// don't even process these message types
out.add(msg);
return;
}
// because we NORMALLY pass around just the object (there is no serialization going on...) we have to explicitly check to see
// if this object, or any of it's fields MIGHT HAVE BEEN an RMI Proxy (or should be on), and switcheroo it here.
// NORMALLY this is automatic since the kryo IDs on each side point to the "correct object" for serialization, but here we don't do that.
// maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections)
@SuppressWarnings("unchecked")
IdentityMap<Object, Object> implToProxy = implToProxyREF.get(this);
IdentityMap<Object, Field[]> objectHasRemoteObjects = remoteObjectREF.get(this);
Object proxy = implToProxy.get(msg);
if (proxy != null) {
// we have a proxy object. nothing left to do.
out.add(proxy);
return;
}
Class<?> messageClass = msg.getClass();
// are there any fields of this message class that COULD contain remote object fields? (NOTE: not RMI fields yet...)
final Field[] remoteObjectFields = objectHasRemoteObjects.get(messageClass);
if (remoteObjectFields == null) {
// maybe one of it's fields is a proxy object?
// we cache the fields that have to be replaced, so subsequent invocations are significantly more preformat
final ArrayList<Field> fields = new ArrayList<Field>();
// we have to walk the hierarchy of this object to check ALL fields, public and private, using getDeclaredFields()
while (messageClass != Object.class) {
// this will get ALL fields that are
for (Field field : messageClass.getDeclaredFields()) {
final Class<?> type = field.getType();
if (type.isInterface()) {
boolean prev = field.isAccessible();
final Object o;
try {
field.setAccessible(true);
o = field.get(msg);
if (o instanceof RemoteObject) {
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
int id = handler.objectID;
field.set(msg, connection.getImplementationObject(id));
fields.add(field);
}
else {
// is a field supposed to be a proxy?
proxy = implToProxy.get(o);
if (proxy != null) {
field.set(msg, proxy);
fields.add(field);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
} finally {
field.setAccessible(prev);
}
}
}
messageClass = messageClass.getSuperclass();
}
Field[] array;
if (fields.isEmpty()) {
// no need to ever process this class again.
array = NO_REMOTE_FIELDS;
}
else {
array = fields.toArray(new Field[fields.size()]);
}
//noinspection SynchronizeOnNonFinalField
synchronized (objectHasRemoteObjects) {
// i know what I'm doing. This must be synchronized.
objectHasRemoteObjects.put(messageClass, array);
}
}
else if (remoteObjectFields != NO_REMOTE_FIELDS) {
// quickly replace objects as necessary
for (Field field : remoteObjectFields) {
boolean prev = field.isAccessible();
final Object o;
try {
field.setAccessible(true);
o = field.get(msg);
if (o instanceof RemoteObject) {
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o);
int id = handler.objectID;
field.set(msg, connection.getImplementationObject(id));
}
else {
// is a field supposed to be a proxy?
proxy = implToProxy.get(o);
if (proxy != null) {
field.set(msg, proxy);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
// logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e);
} finally {
field.setAccessible(prev);
}
}
}
out.add(msg);
}
private
void receivedRegistration(final ConnectionImpl connection, final RmiRegistration registration) {
// manage creating/getting/notifying this RMI object
// these fields are ALWAYS present!
final Class<?> interfaceClass = registration.interfaceClass;
final int callbackId = registration.callbackId;
if (registration.isRequest) {
// Check if we are creating a new REMOTE object. This check is always first.
if (registration.rmiId == RmiBridge.INVALID_RMI) {
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation)
//
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
// have to convert the iFace -> Impl
EndPointBase<Connection> endPoint = connection.getEndPoint();
CryptoSerializationManager serialization = endPoint.getSerialization();
Class<?> rmiImpl = serialization.getRmiImpl(registration.interfaceClass);
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, rmiImpl, callbackId);
connection.TCP(registrationResult)
.flush();
}
// Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object
else {
// THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist)
//
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId);
connection.TCP(registrationResult)
.flush();
}
}
else {
// this is the response.
// THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client.
// on "local" connections (as opposed to "network" connections), the objects ARE NOT serialized, so we never
// generate a proxy via the rmiID - so the LocalRmiProxy (in this use case) overwrites the remoteObject with a proxy object
// if we are the response, we want to create a proxy object instead of just passing the ACTUAL object.
// On the "network" (RemoteObjectSerializer.java) stack, this process is automatic -- and here we have to mimic this behavior.
// has to be 'registration.remoteObject' because we use it later on
if (ENABLE_PROXY_OBJECTS && registration.remoteObject != null) {
// override the implementation object with the proxy. This is required because RMI must be the same between "network" and "local"
// connections -- even if this "slows down" the speed/performance of what "local" connections offer.
RemoteObject proxyObject = connection.getProxyObject(registration.rmiId, interfaceClass);
// have to save A and B so we can correctly switch as necessary
//noinspection SynchronizeOnNonFinalField
synchronized (implToProxy) {
// i know what I'm doing. This must be synchronized.
implToProxy.put(registration.remoteObject, proxyObject);
}
connection.runRmiCallback(interfaceClass, callbackId, proxyObject);
}
else {
connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject);
}
}
}
// private
// LocalRmiClassEncoder replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class<?> implClass) {
// Field[] rmiFields = fieldCache.get(implClass);
// int length = rmiFields.length;
//
// Object rmiObject = null;
// int[] rmiFieldIds = new int[length];
// for (int i = 0; i < length; i++) {
// Field field = rmiFields[i];
//
// // if it's an RMI object we want to write out a proxy object in the field instead of the actual object
// try {
// rmiObject = field.get(object);
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// }
//
// if (rmiObject == null) {
// rmiFieldIds[i] = 0; // 0 means it was null
// }
//
// final Map<Object, Integer> localWeakCache = objectThreadLocals.get();
//
// Integer id = localWeakCache.get(rmiObject);
// if (id == null) {
// // duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
// int registeredId = connection.getRegisteredId(rmiObject);
// rmiFieldIds[i] = registeredId;
// localWeakCache.put(rmiObject, registeredId);
// }
// else {
// rmiFieldIds[i] = id;
// }
// }
//
// LocalRmiClassEncoder localRmiClassEncoder = new LocalRmiClassEncoder();
// localRmiClassEncoder.rmiObject = object;
// localRmiClassEncoder.rmiFieldIds = rmiFieldIds;
//
// return localRmiClassEncoder;
// }
// Field[] getFields(final Class<?> clazz) {
// // duplicates are OK, because they will contain the same information, so we DO NOT care about single writers
//
// //noinspection unchecked
// final IdentityMap<Class<?>, Field[]> identityMap = rmiFieldsREF.get(this);
//
//
// Field[] rmiFields = identityMap.get(clazz);
// if (rmiFields != null) {
// return rmiFields;
// }
//
// final ArrayList<Field> fields = new ArrayList<Field>();
//
// for (Field field : clazz.getDeclaredFields()) {
// if (field.getAnnotation(Rmi.class) != null) {
// fields.add(field);
// }
// }
//
//
// rmiFields = new Field[fields.size()];
// fields.toArray(rmiFields);
//
// // save in cache
// fieldCache.put(clazz, rmiFields);
// rmiFieldsREF.lazySet(this, fieldCache);
//
// return rmiFields;
// }
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection;
import dorkbox.network.rmi.RmiBridge;
import dorkbox.network.rmi.RmiRegistration;
class RegisterRmiNetworkHandler implements Listener.OnMessageReceived<ConnectionImpl, RmiRegistration> {
RegisterRmiNetworkHandler() {
}
@Override
public
void received(final ConnectionImpl connection, final RmiRegistration registration) {
// manage creating/getting/notifying this RMI object
// these fields are ALWAYS present!
final Class<?> interfaceClass = registration.interfaceClass;
final int callbackId = registration.callbackId;
if (registration.isRequest) {
// Check if we are creating a new REMOTE object. This check is always first.
if (registration.rmiId == RmiBridge.INVALID_RMI) {
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation)
//
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
// For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId);
connection.TCP(registrationResult).flush();
}
// Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object
else {
// THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist)
//
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId);
connection.TCP(registrationResult).flush();
}
}
else {
// this is the response.
// THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client.
connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject);
}
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection;
import dorkbox.network.rmi.RmiRegistration;
class RegisterRmiSystemListener implements Listener.OnMessageReceived<ConnectionImpl, RmiRegistration> {
RegisterRmiSystemListener() {
}
@Override
public
void received(final ConnectionImpl connection, final RmiRegistration registerClass) {
// register this into the RmiBridge
connection.registerInternal(connection, registerClass);
}
}

View File

@ -18,23 +18,18 @@ package dorkbox.network.connection.registration.local;
import static dorkbox.network.connection.EndPointBase.maxShutdownWaitTimeInMilliSeconds;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.RegisterRmiLocalHandler;
import dorkbox.network.connection.RegistrationWrapper;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.RegistrationHandler;
import dorkbox.network.pipeline.rmi.LocalRmiDecoder;
import dorkbox.network.pipeline.rmi.LocalRmiEncoder;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
public abstract
class RegistrationLocalHandler<C extends Connection> extends RegistrationHandler<C> {
protected static final String LOCAL_RMI_ENCODER = "localRmiEncoder";
protected static final String LOCAL_RMI_DECODER = "localRmiDecoder";
static final String LOCAL_RMI_HANDLER = "localRmiHandler";
final RegisterRmiLocalHandler rmiLocalHandler = new RegisterRmiLocalHandler();
protected final LocalRmiEncoder encoder = new LocalRmiEncoder();
protected final LocalRmiDecoder decoder = new LocalRmiDecoder();
public
RegistrationLocalHandler(String name, RegistrationWrapper<C> registrationWrapper) {
super(name, registrationWrapper);
}

View File

@ -84,13 +84,7 @@ class RegistrationLocalHandlerClient<C extends Connection> extends RegistrationL
///////////////////////
// DECODE (or upstream)
///////////////////////
pipeline.addFirst(LOCAL_RMI_ENCODER, decoder);
/////////////////////////
// ENCODE (or downstream)
/////////////////////////
pipeline.addFirst(LOCAL_RMI_DECODER, encoder);
pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler);
}
// have to setup connection handler

View File

@ -83,13 +83,7 @@ class RegistrationLocalHandlerServer<C extends Connection> extends RegistrationL
///////////////////////
// DECODE (or upstream)
///////////////////////
pipeline.addFirst(LOCAL_RMI_ENCODER, decoder);
/////////////////////////
// ENCODE (or downstream)
/////////////////////////
pipeline.addFirst(LOCAL_RMI_DECODER, encoder);
pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler);
}
// have to setup connection handler

View File

@ -1,30 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.pipeline.rmi;
/**
* This is for a LOCAL connection (same-JVM)
*/
class LocalRmiClassEncoder {
public Object rmiObject;
// these are the "in order" rmi ID's for the fields of 'rmiObject'
public int[] rmiFieldIds;
public
LocalRmiClassEncoder() {
}
}

View File

@ -1,95 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.pipeline.rmi;
import java.lang.reflect.Field;
import java.util.List;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.rmi.RemoteObject;
import dorkbox.network.serialization.CryptoSerializationManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
/**
* This is for a LOCAL connection (same-JVM)
*/
public
class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE();
public
LocalRmiDecoder() {
super();
}
@Override
protected
void decode(final ChannelHandlerContext context, final Object msg, final List<Object> out) throws Exception {
if (msg instanceof LocalRmiClassEncoder) {
LocalRmiClassEncoder encoded = (LocalRmiClassEncoder) msg;
final Object messageObject = encoded.rmiObject;
final int[] rmiFieldIds = encoded.rmiFieldIds;
final Class<?> messageClass = messageObject.getClass();
ConnectionImpl connection = (ConnectionImpl) context.pipeline()
.last();
Object localRmiObject = null;
Field field;
int registeredId;
final Field[] rmiFields = fieldCache.get(messageClass);
for (int i = 0; i < rmiFields.length; i++) {
field = rmiFields[i];
registeredId = rmiFieldIds[i];
if (registeredId == 0) {
// the field was null/ignore
} else {
// if it's an RMI object we want to write out a proxy object in the field instead of the actual object
try {
localRmiObject = field.get(messageObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (localRmiObject == null) {
throw new RuntimeException("Unable to get RMI interface object for RMI implementation");
}
CryptoSerializationManager serialization = connection.getEndPoint()
.getSerialization();
// TODO: see what's up with this.
final Class<?> iface = serialization.getRmiIface(localRmiObject.getClass());
if (iface == null) {
throw new RuntimeException("Unable to get interface for RMI implementation");
}
RemoteObject remoteObject = connection.getProxyObject(registeredId, iface);
field.set(messageObject, remoteObject);
}
}
out.add(messageObject);
} else {
out.add(msg);
}
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.pipeline.rmi;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.rmi.Rmi;
import dorkbox.util.FastThreadLocal;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
/**
* This is for a LOCAL connection (same-JVM)
*/
@Sharable
public
class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
private static final Map<Class<?>, Boolean> transformObjectCache = new ConcurrentHashMap<Class<?>, Boolean>(EndPointBase.DEFAULT_THREAD_POOL_SIZE);
private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE();
private final FastThreadLocal<Map<Object, Integer>> objectThreadLocals = new FastThreadLocal<Map<Object, Integer>>() {
@Override
public
Map<Object, Integer> initialValue() {
return new WeakHashMap<Object, Integer>(8);
}
};
public
LocalRmiEncoder() {
super();
}
@Override
protected
void encode(final ChannelHandlerContext context, final Object msg, final List<Object> out) throws Exception {
// have to change the rmi objects to proxy objects, with the server-assigned ID
// HOWEVER -- we cannot use the connection here (otherwise the logic is backwards). The connection must be set in other side
// we should check to see if this class is registered as having RMI methods present.
// if YES, then we have to send the corresponding RMI proxy object INSTEAD of the actual object.
// normally, it's OK to send the actual object.
//
// We specifically DO NOT do full serialization because it's not necessary --- we are running inside the same JVM.
final Class<?> implClass = msg.getClass();
Boolean needsTransform = transformObjectCache.get(implClass);
if (needsTransform == null) {
boolean hasRmi = implClass.getAnnotation(Rmi.class) != null;
if (hasRmi) {
// replace object
ConnectionImpl connection = (ConnectionImpl) context.pipeline()
.last();
out.add(replaceFieldObjects(connection, msg, implClass));
}
else {
transformObjectCache.put(implClass, Boolean.FALSE);
out.add(msg);
}
}
else if (needsTransform) {
ConnectionImpl connection = (ConnectionImpl) context.pipeline()
.last();
// replace object
out.add(replaceFieldObjects(connection, msg, implClass));
} else {
out.add(msg);
}
}
private
Object replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class<?> implClass) {
Field[] rmiFields = fieldCache.get(implClass);
int length = rmiFields.length;
Object rmiObject = null;
int[] rmiFieldIds = new int[length];
for (int i = 0; i < length; i++) {
Field field = rmiFields[i];
// if it's an RMI object we want to write out a proxy object in the field instead of the actual object
try {
rmiObject = field.get(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (rmiObject == null) {
rmiFieldIds[i] = 0; // 0 means it was null
}
final Map<Object, Integer> localWeakCache = objectThreadLocals.get();
Integer id = localWeakCache.get(rmiObject);
if (id == null) {
// duplicates are fine, as they represent the same object (as specified by the ID) on the remote side.
int registeredId = connection.getRegisteredId(rmiObject);
rmiFieldIds[i] = registeredId;
localWeakCache.put(rmiObject, registeredId);
} else {
rmiFieldIds[i] = id;
}
}
LocalRmiClassEncoder localRmiClassEncoder = new LocalRmiClassEncoder();
localRmiClassEncoder.rmiObject = object;
localRmiClassEncoder.rmiFieldIds = rmiFieldIds;
return localRmiClassEncoder;
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.pipeline.rmi;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import com.esotericsoftware.kryo.util.IdentityMap;
import dorkbox.network.rmi.Rmi;
/**
* Uses the "single writer principle" for fast access, but disregards 'single writer', because duplicates are OK
*
* This is for a LOCAL connection (same-JVM)
*
*/
class RmiFieldCache {
private volatile IdentityMap<Class<?>, Field[]> fieldCache = new IdentityMap<Class<?>, Field[]>();
private static final AtomicReferenceFieldUpdater<RmiFieldCache, IdentityMap> rmiFieldsREF =
AtomicReferenceFieldUpdater.newUpdater(RmiFieldCache.class,
IdentityMap.class,
"fieldCache");
private static final RmiFieldCache INSTANCE = new RmiFieldCache();
public static synchronized RmiFieldCache INSTANCE() {
return INSTANCE;
}
private
RmiFieldCache() {
}
Field[] get(final Class<?> clazz) {
// duplicates are OK, because they will contain the same information, so we DO NOT care about single writers
//noinspection unchecked
final IdentityMap<Class<?>, Field[]> identityMap = rmiFieldsREF.get(this);
Field[] rmiFields = identityMap.get(clazz);
if (rmiFields != null) {
return rmiFields;
}
final ArrayList<Field> fields = new ArrayList<Field>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(Rmi.class) != null) {
fields.add(field);
}
}
rmiFields = new Field[fields.size()];
fields.toArray(rmiFields);
// save in cache
fieldCache.put(clazz, rmiFields);
return rmiFields;
}
}

View File

@ -22,8 +22,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This specifies to the serializer, that this class contains an RMI object, and that a specific field is an RMI object. Both are
* necessary.
* This specifies to the RMI system that a specific field in this RMI object is ALSO an RMI object
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited

View File

@ -60,7 +60,7 @@ import dorkbox.util.collections.ObjectIntMap;
* <p/>
* <p/>
* Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link
* Connection#getRemoteObject(Class, RemoteObjectCallback)} for the registered objects.
* Connection#createRemoteObject(Class, RemoteObjectCallback)} for the registered objects.
* <p/>
* It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a return value which
* is not {@link RemoteObject#setAsync(boolean) ignored}, an extra byte is written. If the type of a parameter is not final (note that
@ -414,7 +414,7 @@ class RmiBridge {
}
/**
* Warning. This is an advanced method. You should probably be using {@link Connection#getRemoteObject(Class, RemoteObjectCallback)}
* Warning. This is an advanced method. You should probably be using {@link Connection#createRemoteObject(Class, RemoteObjectCallback)}
* <p>
* <p>
* Returns a proxy object that implements the specified interfaces. Methods invoked on the proxy object will be invoked remotely on the
@ -432,25 +432,29 @@ class RmiBridge {
* have the proxy object replaced with the registered object.
*
* @see RemoteObject
*
* @param connection this is really the network client -- there is ONLY ever 1 connection
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
* @param iFace this is the RMI interface
*/
public
RemoteObject createProxyObject(Connection connection, int objectID, Class<?> iface) {
RemoteObject createProxyObject(Connection connection, int objectID, Class<?> iFace) {
if (connection == null) {
throw new IllegalArgumentException("connection cannot be null.");
}
if (iface == null) {
if (iFace == null) {
throw new IllegalArgumentException("iface cannot be null.");
}
if (!iface.isInterface()) {
if (!iFace.isInterface()) {
throw new IllegalArgumentException("iface must be an interface.");
}
Class<?>[] temp = new Class<?>[2];
temp[0] = RemoteObject.class;
temp[1] = iface;
temp[1] = iFace;
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
temp,
new RmiProxyHandler(connection, objectID, iface));
new RmiProxyHandler(connection, objectID, iFace));
}
}

View File

@ -56,6 +56,7 @@ import dorkbox.network.serialization.RmiSerializationManager;
* <p>
* The only methods than can be invoked are INTERFACE methods and OBJECT methods
*/
public
class RmiProxyHandler implements InvocationHandler {
private final Logger logger;
@ -66,7 +67,6 @@ class RmiProxyHandler implements InvocationHandler {
private final boolean[] pendingResponses = new boolean[64];
private final Connection connection;
private final Class<?> iFace;
public final int objectID; // this is the RMI id
public final int ID; // this is the KRYO id
@ -96,7 +96,6 @@ class RmiProxyHandler implements InvocationHandler {
super();
this.connection = connection;
this.iFace = iFace;
this.objectID = objectID;
this.proxyString = "<proxy #" + objectID + ">";

View File

@ -20,13 +20,27 @@ package dorkbox.network.rmi;
*/
public
class RmiRegistration {
public boolean isRequest;
/**
* this is null if there are problems creating an object on the remote side, otherwise it is non-null.
*/
public Object remoteObject;
/**
* this is used to create a NEW rmi object on the REMOTE side (these are bound the to connection. They are NOT GLOBAL, ie: available on all connections)
*/
public Class<?> interfaceClass;
// this is used to get specific, GLOBAL rmi objects (objects that are not bound to a single connection)
public int remoteObjectId;
/**
* this is used to get specific, GLOBAL rmi objects (objects that are not bound to a single connection)
*/
public int rmiId;
public int rmiID;
/**
* this is the callback ID assigned by the LOCAL side, to know WHICH RMI callback to call when we have a remote object available
*/
public int callbackId;
@SuppressWarnings("unused")
private
@ -34,35 +48,35 @@ class RmiRegistration {
// for serialization
}
// When requesting a new remote object to be created.
// SENT FROM "client" -> "server"
/**
* When requesting a new or existing remote object
* SENT FROM "local" -> "remote"
*
* @param interfaceClass the class to create
* @param rmiId the RMI id to get from the REMOTE side
* @param callbackId the rmi callback ID on the LOCAL side, to know which callback to use
*/
public
RmiRegistration(final Class<?> interfaceClass, final int rmiID) {
RmiRegistration(final Class<?> interfaceClass, final int rmiId, final int callbackId) {
isRequest = true;
this.interfaceClass = interfaceClass;
this.rmiID = rmiID;
}
// When requesting a new remote object to be created.
// SENT FROM "client" -> "server"
public
RmiRegistration(final int remoteObjectId, final int rmiID) {
this.remoteObjectId = remoteObjectId;
this.rmiID = rmiID;
this.rmiId = rmiId;
this.callbackId = callbackId;
}
// When there was an error creating the remote object.
// SENT FROM "server" -> "client"
/**
* This is when we successfully created a new object (if there was an error, remoteObject is null)
* SENT FROM "remote" -> "local"
*
* @param callbackId the rmi callback ID on the LOCAL side, to know which callback to use
*/
public
RmiRegistration(final int rmiID) {
this.rmiID = rmiID;
}
// This is when we successfully created a new object
// SENT FROM "server" -> "client"
public
RmiRegistration(final Object remoteObject, final int rmiID) {
RmiRegistration(final Class<?> interfaceClass, final int rmiId, final int callbackId, final Object remoteObject) {
isRequest = false;
this.interfaceClass = interfaceClass;
this.rmiId = rmiId;
this.callbackId = callbackId;
this.remoteObject = remoteObject;
this.rmiID = rmiID;
}
}

View File

@ -42,7 +42,7 @@ import java.io.IOException;
*
* @author Nathan Sweet <misc@n4te.com>
* @see dorkbox.network.connection.Connection#getRemoteObject(int, RemoteObjectCallback)
* @see dorkbox.network.connection.Connection#getRemoteObject(Class, RemoteObjectCallback)
* @see dorkbox.network.connection.Connection#createRemoteObject(Class, RemoteObjectCallback)
*/
public
class TimeoutException extends IOException {

View File

@ -261,8 +261,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag
private RemoteObjectSerializer remoteObjectSerializer;
// used to track which interface -> implementation, for use by RMI
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiKryoIdToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiKryoIdToIface = new IntMap<Class<?>>();
private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
@ -380,8 +380,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
// if this is over-written, we don't care.
rmiIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
rmiIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
rmiKryoIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
rmiKryoIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
}
}
@ -575,8 +575,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag
*/
@Override
public
Class<?> getRmiIface(Class<?> implementation) {
return rmiImplToIface.get(implementation);
Class<?> getRmiImpl(Class<?> iface) {
return rmiIfaceToImpl.get(iface);
}
/**
@ -804,6 +804,12 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag
return methodCache.get(classId);
}
@Override
public
boolean isRmiEnabled() {
return usesRmi;
}
@Override
public synchronized
boolean initialized() {