RMI working now
This commit is contained in:
parent
e84688ba85
commit
f8d71b96dd
|
@ -42,7 +42,7 @@ import com.esotericsoftware.kryo.util.IntMap;
|
|||
import com.esotericsoftware.kryo.util.MapReferenceResolver;
|
||||
|
||||
import dorkbox.network.connection.ping.PingMessage;
|
||||
import dorkbox.network.rmi.CachedMethod;
|
||||
import dorkbox.network.rmi.ClassDefinitions;
|
||||
import dorkbox.network.rmi.InvocationHandlerSerializer;
|
||||
import dorkbox.network.rmi.InvocationResultSerializer;
|
||||
import dorkbox.network.rmi.InvokeMethod;
|
||||
|
@ -153,9 +153,11 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
}
|
||||
|
||||
private static class RemoteImplClass {
|
||||
private final Class<?> ifaceClass;
|
||||
private final Class<?> implClass;
|
||||
|
||||
RemoteImplClass(final Class<?> implClass) {
|
||||
RemoteImplClass(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
this.ifaceClass = ifaceClass;
|
||||
this.implClass = implClass;
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +176,9 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
private RemoteObjectSerializer remoteObjectSerializer;
|
||||
|
||||
// used to track which interface -> implementation, for use by RMI
|
||||
private final IntMap<Class<?>> rmiInterfaceToImpl = new IntMap<Class<?>>();
|
||||
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
|
||||
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
|
||||
private final ClassDefinitions classDefinitions = new ClassDefinitions();
|
||||
|
||||
/**
|
||||
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
|
||||
|
@ -207,12 +211,12 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
public
|
||||
KryoExtra create() {
|
||||
synchronized (CryptoSerializationManager.this) {
|
||||
KryoExtra kryo = new KryoExtra();
|
||||
KryoExtra kryo = new KryoExtra(CryptoSerializationManager.this);
|
||||
|
||||
// we HAVE to pre-allocate the KRYOs
|
||||
boolean useAsm = !useUnsafeMemory;
|
||||
|
||||
kryo.setAsmEnabled(useAsm);
|
||||
kryo.getFieldSerializerConfig().setUseAsm(useAsm);
|
||||
kryo.setRegistrationRequired(registrationRequired);
|
||||
|
||||
kryo.setReferences(references);
|
||||
|
@ -268,7 +272,8 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
int id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
|
||||
|
||||
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
|
||||
rmiInterfaceToImpl.put(id, remoteImplClass.implClass);
|
||||
rmiIdToImpl.put(id, remoteImplClass.implClass);
|
||||
rmiIdToIface.put(id, remoteImplClass.ifaceClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,38 +360,12 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the previously registered class
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* <p/>
|
||||
* Specifically, 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 dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. If the
|
||||
* type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
*/
|
||||
private
|
||||
Class<?> getLastAddedClass() {
|
||||
// get the previously registered class
|
||||
Object obj = classesToRegister.get(classesToRegister.size() - 1);
|
||||
if (obj instanceof Class) {
|
||||
return (Class) obj;
|
||||
}
|
||||
else if (obj instanceof ClassSerializer) {
|
||||
ClassSerializer classSerializer = (ClassSerializer) obj;
|
||||
return classSerializer.clazz;
|
||||
}
|
||||
else if (obj instanceof ClassSerializer2) {
|
||||
ClassSerializer2 classSerializer = (ClassSerializer2) obj;
|
||||
return classSerializer.clazz;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static
|
||||
boolean testInterface(Class<?> from, Class<?> iface) {
|
||||
for (Class<?> intface : from.getInterfaces()) {
|
||||
if (iface.equals(intface) || testInterface(intface, iface)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass) {
|
||||
|
@ -421,13 +400,11 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
}
|
||||
|
||||
usesRmi = true;
|
||||
classesToRegister.add(new RemoteImplClass(ifaceClass, implClass));
|
||||
|
||||
// THIS IS DONE ON THE SERVER ONLY
|
||||
// we have to save the fact that we might have overridden methods.
|
||||
// will throw IllegalArgumentException if the iface/impl have previously been overridden
|
||||
CachedMethod.registerOverridden(ifaceClass, implClass);
|
||||
// this MUST BE UNIQUE otherwise unexpected things can happen.
|
||||
classDefinitions.set(ifaceClass, implClass);
|
||||
|
||||
classesToRegister.add(new RemoteImplClass(implClass));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -472,6 +449,40 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(int objectId) {
|
||||
return rmiIdToIface.get(objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified interface
|
||||
*
|
||||
* @return the corresponding implementation
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiImpl(Class<?> iface) {
|
||||
return classDefinitions.get(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(Class<?> implementation) {
|
||||
return classDefinitions.getReverse(implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
|
||||
*
|
||||
|
@ -479,8 +490,9 @@ class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializa
|
|||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getRmiImpl(int objectId) {
|
||||
return rmiInterfaceToImpl.get(objectId);
|
||||
public
|
||||
Class<?> getRmiImpl(int objectId) {
|
||||
return rmiIdToImpl.get(objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,14 @@
|
|||
*/
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
|
||||
import dorkbox.network.pipeline.ByteBufInput;
|
||||
import dorkbox.network.pipeline.ByteBufOutput;
|
||||
import dorkbox.util.bytes.BigEndian;
|
||||
|
@ -26,11 +33,6 @@ import io.netty.buffer.Unpooled;
|
|||
import net.jpountz.lz4.LZ4Compressor;
|
||||
import net.jpountz.lz4.LZ4Factory;
|
||||
import net.jpountz.lz4.LZ4FastDecompressor;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Nothing in this class is thread safe
|
||||
|
@ -83,8 +85,11 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
|
|||
private byte[] decompressOutput;
|
||||
private ByteBuf decompressBuf;
|
||||
|
||||
private dorkbox.network.util.CryptoSerializationManager serializationManager;
|
||||
|
||||
public
|
||||
KryoExtra() {
|
||||
KryoExtra(final dorkbox.network.util.CryptoSerializationManager serializationManager) {
|
||||
this.serializationManager = serializationManager;
|
||||
}
|
||||
|
||||
public synchronized
|
||||
|
@ -580,4 +585,9 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
|
|||
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public
|
||||
dorkbox.network.util.CryptoSerializationManager getSerializationManager() {
|
||||
return serializationManager;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,20 +15,18 @@
|
|||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.rmi.OverriddenMethods;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
public
|
||||
class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
|
||||
|
||||
private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE();
|
||||
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
|
||||
|
||||
public
|
||||
LocalRmiDecoder() {
|
||||
|
@ -70,7 +68,10 @@ class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
|
|||
throw new RuntimeException("Unable to get RMI interface object for RMI implementation");
|
||||
}
|
||||
|
||||
final Class<?> iface = overriddenMethods.getReverse(localRmiObject.getClass());
|
||||
|
||||
final Class<?> iface = connection.getEndPoint()
|
||||
.getSerialization()
|
||||
.getRmiIface(localRmiObject.getClass());
|
||||
if (iface == null) {
|
||||
throw new RuntimeException("Unable to get interface for RMI implementation");
|
||||
}
|
||||
|
|
|
@ -15,20 +15,20 @@
|
|||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
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;
|
||||
|
||||
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.EndPoint;
|
||||
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;
|
||||
|
||||
@Sharable
|
||||
public
|
||||
class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
|
||||
|
@ -66,7 +66,7 @@ class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
|
|||
Boolean needsTransform = transformObjectCache.get(implClass);
|
||||
|
||||
if (needsTransform == null) {
|
||||
boolean hasRmi = implClass.getAnnotation(RMI.class) != null;
|
||||
boolean hasRmi = implClass.getAnnotation(Rmi.class) != null;
|
||||
|
||||
if (hasRmi) {
|
||||
// replace object
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import dorkbox.network.rmi.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
|
||||
*/
|
||||
|
@ -57,7 +58,7 @@ class RmiFieldCache {
|
|||
final ArrayList<Field> fields = new ArrayList<Field>();
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getAnnotation(RMI.class) != null) {
|
||||
if (field.getAnnotation(Rmi.class) != null) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import com.esotericsoftware.reflectasm.MethodAccess;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.network.util.RmiSerializationManager;
|
||||
import dorkbox.util.ClassHelper;
|
||||
|
||||
|
@ -96,27 +97,34 @@ class CachedMethod {
|
|||
|
||||
// the purpose of the method cache, is to accelerate looking up methods for specific class
|
||||
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
|
||||
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
|
||||
|
||||
|
||||
// type will be likely be the interface
|
||||
public static
|
||||
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type) {
|
||||
/**
|
||||
* Called when we read a RMI method invocation on the "server" side (by kryo)
|
||||
*
|
||||
* @param type is the implementation type
|
||||
*/
|
||||
static
|
||||
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type, final int classId) {
|
||||
CachedMethod[] cachedMethods = methodCache.get(type);
|
||||
if (cachedMethods != null) {
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
cachedMethods = getCachedMethods(kryo, type);
|
||||
cachedMethods = getCachedMethods(kryo, type, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
|
||||
// type will be likely be the interface
|
||||
public static
|
||||
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type) {
|
||||
/**
|
||||
* Called when we write an RMI method invocation on the "client" side (by RmiProxyHandler)
|
||||
*
|
||||
* @param type this is the interface.
|
||||
*/
|
||||
static
|
||||
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type, final int classId) {
|
||||
CachedMethod[] cachedMethods = methodCache.get(type);
|
||||
if (cachedMethods != null) {
|
||||
return cachedMethods;
|
||||
|
@ -124,7 +132,7 @@ class CachedMethod {
|
|||
|
||||
final KryoExtra kryo = serializationManager.takeKryo();
|
||||
try {
|
||||
cachedMethods = getCachedMethods(kryo, type);
|
||||
cachedMethods = getCachedMethods(kryo, type, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
} finally {
|
||||
serializationManager.returnKryo(kryo);
|
||||
|
@ -133,10 +141,25 @@ class CachedMethod {
|
|||
return cachedMethods;
|
||||
}
|
||||
|
||||
// race-conditions are OK, because we just recreate the same thing.
|
||||
private static
|
||||
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type) {
|
||||
// race-conditions are OK, because we just recreate the same thing.
|
||||
final ArrayList<Method> methods = getMethods(type);
|
||||
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type, final int classId) {
|
||||
// sometimes, the method index is based upon an interface and NOT the implementation. We have to clear that up here.
|
||||
CryptoSerializationManager serialization = ((KryoExtra) kryo).getSerializationManager();
|
||||
|
||||
// when there is an interface available, we want to use that instead of the implementation. This is because the incoming
|
||||
// implementation is ACTUALLY mapped (on the "client" side) to the interface. If we don't use the interface, we will have the
|
||||
// wrong order of methods, so invoking a method by it's index will fail.
|
||||
Class<?> interfaceClass = serialization.getRmiIface(classId);
|
||||
|
||||
final ArrayList<Method> methods;
|
||||
|
||||
if (interfaceClass == null) {
|
||||
methods = getMethods(type);
|
||||
} else {
|
||||
methods = getMethods(interfaceClass);
|
||||
}
|
||||
|
||||
final int size = methods.size();
|
||||
final CachedMethod[] cachedMethods = new CachedMethod[size];
|
||||
|
||||
|
@ -155,8 +178,17 @@ class CachedMethod {
|
|||
|
||||
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
||||
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
|
||||
final IdentityMap<Method, Method> overriddenMethods = getOverriddenMethods(type, methods);
|
||||
final boolean asmEnabled = kryo.getAsmEnabled();
|
||||
|
||||
// will only be valid if implementation type is NOT NULL (otherwise will be null)
|
||||
final IdentityMap<Method, Method> overriddenMethods;
|
||||
if (interfaceClass == null) {
|
||||
overriddenMethods = null;
|
||||
} else {
|
||||
// type here must be the implementation
|
||||
overriddenMethods = getOverriddenMethods(type, methods);
|
||||
}
|
||||
|
||||
final boolean asmEnabled = kryo.getFieldSerializerConfig().isUseAsm();
|
||||
|
||||
MethodAccess methodAccess = null;
|
||||
// reflectASM can't get any method from the 'Object' object, and it MUST be public
|
||||
|
@ -173,6 +205,7 @@ class CachedMethod {
|
|||
for (int i = 0; i < size; i++) {
|
||||
final Method origMethod = methods.get(i);
|
||||
Method method = origMethod; // copy because one or more can be overridden
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
MethodAccess localMethodAccess = methodAccess; // copy because one or more can be overridden
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Class<?>[] asmParameterTypes = parameterTypes;
|
||||
|
@ -184,7 +217,7 @@ class CachedMethod {
|
|||
// the correct object for this overridden method to be called on.
|
||||
method = overriddenMethod;
|
||||
|
||||
Class<?> overrideType = method.getDeclaringClass();
|
||||
Class<?> overrideType = declaringClass;
|
||||
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
|
||||
localMethodAccess = MethodAccess.get(overrideType);
|
||||
asmParameterTypes = method.getParameterTypes();
|
||||
|
@ -203,7 +236,7 @@ class CachedMethod {
|
|||
cachedMethod = asmCachedMethod;
|
||||
} catch (Exception e) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", method.getDeclaringClass(), method.getName(), e);
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, method.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +246,18 @@ class CachedMethod {
|
|||
}
|
||||
cachedMethod.method = method;
|
||||
cachedMethod.origMethod = origMethod;
|
||||
cachedMethod.methodClassID = kryo.getRegistration(method.getDeclaringClass()).getId();
|
||||
|
||||
|
||||
// on the "server", we only register the implementation. NOT THE INTERFACE, so for RMI classes, we have to get the impl
|
||||
Class<?> impl = serialization.getRmiImpl(declaringClass);
|
||||
if (impl != null) {
|
||||
cachedMethod.methodClassID = kryo.getRegistration(impl)
|
||||
.getId();
|
||||
}
|
||||
else {
|
||||
cachedMethod.methodClassID = kryo.getRegistration(declaringClass)
|
||||
.getId();
|
||||
}
|
||||
cachedMethod.methodIndex = i;
|
||||
|
||||
// Store the serializer for each final parameter.
|
||||
|
@ -231,54 +275,48 @@ class CachedMethod {
|
|||
return cachedMethods;
|
||||
}
|
||||
|
||||
// does not null check
|
||||
private static
|
||||
IdentityMap<Method, Method> getOverriddenMethods(final Class<?> type, final ArrayList<Method> origMethods) {
|
||||
final Class<?> implType = overriddenMethods.get(type);
|
||||
final ArrayList<Method> implMethods = getMethods(type);
|
||||
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
|
||||
|
||||
if (implType != null) {
|
||||
final ArrayList<Method> implMethods = getMethods(implType);
|
||||
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
|
||||
for (Method origMethod : origMethods) {
|
||||
String name = origMethod.getName();
|
||||
Class<?>[] origTypes = origMethod.getParameterTypes();
|
||||
int origLength = origTypes.length + 1;
|
||||
|
||||
for (Method origMethod : origMethods) {
|
||||
String name = origMethod.getName();
|
||||
Class<?>[] origTypes = origMethod.getParameterTypes();
|
||||
int origLength = origTypes.length + 1;
|
||||
METHOD_CHECK:
|
||||
for (Method implMethod : implMethods) {
|
||||
String checkName = implMethod.getName();
|
||||
Class<?>[] checkTypes = implMethod.getParameterTypes();
|
||||
int checkLength = checkTypes.length;
|
||||
|
||||
METHOD_CHECK:
|
||||
for (Method implMethod : implMethods) {
|
||||
String checkName = implMethod.getName();
|
||||
Class<?>[] checkTypes = implMethod.getParameterTypes();
|
||||
int checkLength = checkTypes.length;
|
||||
if (origLength != checkLength || !(name.equals(checkName))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (origLength != checkLength || !(name.equals(checkName))) {
|
||||
continue;
|
||||
// checkLength > 0
|
||||
Class<?> shouldBeConnectionType = checkTypes[0];
|
||||
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
|
||||
// now we check to see if our "check" method is equal to our "cached" method + Connection
|
||||
if (checkLength == 1) {
|
||||
overrideMap.put(origMethod, implMethod);
|
||||
break;
|
||||
}
|
||||
|
||||
// checkLength > 0
|
||||
Class<?> shouldBeConnectionType = checkTypes[0];
|
||||
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
|
||||
// now we check to see if our "check" method is equal to our "cached" method + Connection
|
||||
if (checkLength == 1) {
|
||||
overrideMap.put(origMethod, implMethod);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
for (int k = 1; k < checkLength; k++) {
|
||||
if (origTypes[k - 1] == checkTypes[k]) {
|
||||
overrideMap.put(origMethod, implMethod);
|
||||
break METHOD_CHECK;
|
||||
}
|
||||
else {
|
||||
for (int k = 1; k < checkLength; k++) {
|
||||
if (origTypes[k - 1] == checkTypes[k]) {
|
||||
overrideMap.put(origMethod, implMethod);
|
||||
break METHOD_CHECK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overrideMap;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
return overrideMap;
|
||||
}
|
||||
|
||||
private static
|
||||
|
@ -314,18 +352,6 @@ class CachedMethod {
|
|||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the SerializationManager, so that RMI classes that are overridden for serialization purposes, can check to see if certain
|
||||
* methods need to be overridden.
|
||||
* <p>
|
||||
* NOTE: It is CRITICAL that this is unique per JVM, otherwise unexpected things can happen.
|
||||
*
|
||||
* @throws IllegalArgumentException if the iface/impl have previously been overridden
|
||||
*/
|
||||
public static
|
||||
void registerOverridden(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
overriddenMethods.set(ifaceClass, implClass);
|
||||
}
|
||||
|
||||
public Method method;
|
||||
public int methodClassID;
|
||||
|
|
|
@ -23,28 +23,23 @@ import com.esotericsoftware.kryo.util.IdentityMap;
|
|||
* Uses the "single writer principle" for fast access
|
||||
*/
|
||||
public
|
||||
class OverriddenMethods {
|
||||
class ClassDefinitions {
|
||||
// not concurrent because they are setup during system initialization
|
||||
private volatile IdentityMap<Class<?>, Class<?>> overriddenMethods = new IdentityMap<Class<?>, Class<?>>();
|
||||
private volatile IdentityMap<Class<?>, Class<?>> overriddenReverseMethods = new IdentityMap<Class<?>, Class<?>>();
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
|
||||
private static final AtomicReferenceFieldUpdater<ClassDefinitions, IdentityMap> overriddenMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassDefinitions.class,
|
||||
IdentityMap.class,
|
||||
"overriddenMethods");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenReverseMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
|
||||
private static final AtomicReferenceFieldUpdater<ClassDefinitions, IdentityMap> overriddenReverseMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(ClassDefinitions.class,
|
||||
IdentityMap.class,
|
||||
"overriddenReverseMethods");
|
||||
|
||||
private static final OverriddenMethods INSTANCE = new OverriddenMethods();
|
||||
public static synchronized OverriddenMethods INSTANCE() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private
|
||||
OverriddenMethods() {
|
||||
public
|
||||
ClassDefinitions() {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +71,7 @@ class OverriddenMethods {
|
|||
Class<?> a = this.overriddenMethods.put(ifaceClass, implClass);
|
||||
Class<?> b = this.overriddenReverseMethods.put(implClass, ifaceClass);
|
||||
|
||||
// this MUST BE UNIQUE per JVM, otherwise unexpected things can happen.
|
||||
// this MUST BE UNIQUE otherwise unexpected things can happen.
|
||||
if (a != null || b != null) {
|
||||
throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " +
|
||||
"because they have already been overridden by something else. It is critical that they are" +
|
|
@ -54,16 +54,22 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
@SuppressWarnings("rawtypes")
|
||||
public
|
||||
void write(final Kryo kryo, final Output output, final InvokeMethod object) {
|
||||
// System.err.println(":: objectID " + object.objectID);
|
||||
// System.err.println(":: methodClassID " + object.cachedMethod.methodClassID);
|
||||
// System.err.println(":: methodIndex " + object.cachedMethod.methodIndex);
|
||||
|
||||
output.writeInt(object.objectID, true);
|
||||
output.writeInt(object.cachedMethod.methodClassID, true);
|
||||
output.writeByte(object.cachedMethod.methodIndex);
|
||||
|
||||
Serializer[] serializers = object.cachedMethod.serializers;
|
||||
int length = serializers.length;
|
||||
|
||||
Object[] args = object.args;
|
||||
|
||||
int i = 0, n = serializers.length;
|
||||
for (; i < n; i++) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
Serializer serializer = serializers[i];
|
||||
|
||||
if (serializer != null) {
|
||||
kryo.writeObjectOrNull(output, args[i], serializer);
|
||||
}
|
||||
|
@ -84,20 +90,22 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
|
||||
int methodClassID = input.readInt(true);
|
||||
Class<?> methodClass = kryo.getRegistration(methodClassID)
|
||||
.getType();
|
||||
.getType();
|
||||
|
||||
byte methodIndex = input.readByte();
|
||||
CachedMethod cachedMethod;
|
||||
try {
|
||||
invokeMethod.cachedMethod = CachedMethod.getMethods(kryo, methodClass)[methodIndex];
|
||||
cachedMethod = CachedMethod.getMethods(kryo, methodClass, methodClassID)[methodIndex];
|
||||
invokeMethod.cachedMethod = cachedMethod;
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
|
||||
}
|
||||
|
||||
CachedMethod cachedMethod = invokeMethod.cachedMethod;
|
||||
Serializer<?>[] serializers = cachedMethod.serializers;
|
||||
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
|
||||
Object[] args = new Object[serializers.length];
|
||||
invokeMethod.args = args;
|
||||
|
||||
for (int i = 0, n = args.length; i < n; i++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
|
|
|
@ -42,7 +42,7 @@ import com.esotericsoftware.kryo.io.Output;
|
|||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
/**
|
||||
* Serializes an object registered with the RmiImplHandler so the receiving side gets a {@link RemoteObject} proxy rather than the bytes for the
|
||||
* Serializes an object registered with the RmiBridge so the receiving side gets a {@link RemoteObject} proxy rather than the bytes for the
|
||||
* serialized object.
|
||||
*
|
||||
* @author Nathan Sweet <misc@n4te.com>
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
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
|
||||
|
@ -32,4 +36,4 @@ import java.lang.annotation.*;
|
|||
@Inherited
|
||||
@Target(value = {ElementType.TYPE, ElementType.FIELD})
|
||||
public
|
||||
@interface RMI {}
|
||||
@interface Rmi {}
|
|
@ -87,7 +87,7 @@ import dorkbox.util.collections.ObjectIntMap;
|
|||
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
|
||||
*/
|
||||
public final
|
||||
class RmiImplHandler {
|
||||
class RmiBridge {
|
||||
public static final int INVALID_RMI = 0;
|
||||
static final int returnValueMask = 1 << 7;
|
||||
static final int returnExceptionMask = 1 << 6;
|
||||
|
@ -105,7 +105,7 @@ class RmiImplHandler {
|
|||
return (objectId & 1) != 0;
|
||||
}
|
||||
|
||||
// the name of who created this RmiImplHandler
|
||||
// the name of who created this RmiBridge
|
||||
private final org.slf4j.Logger logger;
|
||||
|
||||
// we start at 1, because 0 (INVALID_RMI) means we access connection only objects
|
||||
|
@ -130,7 +130,7 @@ class RmiImplHandler {
|
|||
final Object target = connection.getImplementationObject(objectID);
|
||||
|
||||
if (target == null) {
|
||||
Logger logger2 = RmiImplHandler.this.logger;
|
||||
Logger logger2 = RmiBridge.this.logger;
|
||||
if (logger2.isWarnEnabled()) {
|
||||
logger2.warn("Ignoring remote invocation request for unknown object ID: {}", objectID);
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ class RmiImplHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
Executor executor2 = RmiImplHandler.this.executor;
|
||||
Executor executor2 = RmiBridge.this.executor;
|
||||
if (executor2 == null) {
|
||||
try {
|
||||
invoke(connection, target, invokeMethod);
|
||||
|
@ -163,18 +163,18 @@ class RmiImplHandler {
|
|||
};
|
||||
|
||||
/**
|
||||
* Creates an RmiImplHandler with no connections. Connections must be {@link RmiImplHandler#register(int, Object)} added to allow the remote end
|
||||
* Creates an RmiBridge with no connections. Connections must be {@link RmiBridge#register(int, Object)} added to allow the remote end
|
||||
* of the connections to access objects in this ObjectSpace.
|
||||
*
|
||||
* @param executor
|
||||
* Sets the executor used to invoke methods when an invocation is received from a remote endpoint. By default, no
|
||||
* executor is set and invocations occur on the network thread, which should not be blocked for long, May be null.
|
||||
* @param isGlobal
|
||||
* specify if this RmiImplHandler is a "global" bridge, meaning connections will prefer objects from this bridge instead of
|
||||
* specify if this RmiBridge is a "global" bridge, meaning connections will prefer objects from this bridge instead of
|
||||
* the connection-local bridge.
|
||||
*/
|
||||
public
|
||||
RmiImplHandler(final org.slf4j.Logger logger, final Executor executor, final boolean isGlobal) {
|
||||
RmiBridge(final org.slf4j.Logger logger, final Executor executor, final boolean isGlobal) {
|
||||
this.logger = logger;
|
||||
this.executor = executor;
|
||||
|
||||
|
@ -197,7 +197,7 @@ class RmiImplHandler {
|
|||
|
||||
/**
|
||||
* Invokes the method on the object and, if necessary, sends the result back to the connection that made the invocation request. This
|
||||
* method is invoked on the update thread of the {@link EndPoint} for this RmiImplHandler and unless an executor has been set.
|
||||
* method is invoked on the update thread of the {@link EndPoint} for this RmiBridge and unless an executor has been set.
|
||||
*
|
||||
* @param connection
|
||||
* The remote side of this connection requested the invocation.
|
||||
|
@ -298,13 +298,13 @@ class RmiImplHandler {
|
|||
int value = rmiObjectIdCounter.getAndAdd(2);
|
||||
if (value > MAX_RMI_VALUE) {
|
||||
rmiObjectIdCounter.set(MAX_RMI_VALUE); // prevent wrapping by spammy callers
|
||||
logger.error("RMI next value has exceeded maximum limits in RmiImplHandler!");
|
||||
logger.error("RMI next value has exceeded maximum limits in RmiBridge!");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an object to allow the remote end of the RmiImplHandler connections to access it using the specified ID.
|
||||
* Registers an object to allow the remote end of the RmiBridge connections to access it using the specified ID.
|
||||
*
|
||||
* @param objectID
|
||||
* Must not be Integer.MAX_VALUE.
|
||||
|
@ -319,7 +319,7 @@ class RmiImplHandler {
|
|||
throw new IllegalArgumentException("object cannot be null.");
|
||||
}
|
||||
|
||||
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
this.idToObject.put(objectID, object);
|
||||
|
@ -334,12 +334,12 @@ class RmiImplHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the RmiImplHandler connection will no longer be able to access it.
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
@SuppressWarnings("AutoBoxing")
|
||||
public
|
||||
void remove(int objectID) {
|
||||
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
Object object = this.idToObject.remove(objectID);
|
||||
|
@ -356,12 +356,12 @@ class RmiImplHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes an object. The remote end of the RmiImplHandler connection will no longer be able to access it.
|
||||
* Removes an object. The remote end of the RmiBridge connection will no longer be able to access it.
|
||||
*/
|
||||
@SuppressWarnings("AutoBoxing")
|
||||
public
|
||||
void remove(Object object) {
|
||||
WriteLock writeLock = RmiImplHandler.this.objectLock.writeLock();
|
||||
WriteLock writeLock = RmiBridge.this.objectLock.writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
if (!this.idToObject.containsValue(object, true)) {
|
||||
|
@ -417,7 +417,7 @@ class RmiImplHandler {
|
|||
* <p>
|
||||
* Returns a proxy object that implements the specified interfaces. Methods invoked on the proxy object will be invoked remotely on the
|
||||
* object with the specified ID in the ObjectSpace for the specified connection. If the remote end of the connection has not {@link
|
||||
* RmiImplHandler#register(int, Object)} added the connection to the ObjectSpace, the remote method invocations will be ignored.
|
||||
* RmiBridge#register(int, Object)} added the connection to the ObjectSpace, the remote method invocations will be ignored.
|
||||
* <p/>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
|
||||
* RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
|
@ -444,7 +444,7 @@ class RmiImplHandler {
|
|||
temp[0] = RemoteObject.class;
|
||||
temp[1] = iface;
|
||||
|
||||
return (RemoteObject) Proxy.newProxyInstance(RmiImplHandler.class.getClassLoader(),
|
||||
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
|
||||
temp,
|
||||
new RmiProxyHandler(connection, objectID));
|
||||
}
|
|
@ -207,7 +207,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
}
|
||||
|
||||
EndPoint endPoint = this.connection.getEndPoint();
|
||||
final RmiSerializationManager serializationManager = (RmiSerializationManager) endPoint.getSerialization();
|
||||
final RmiSerializationManager serializationManager = endPoint.getSerialization();
|
||||
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
invokeMethod.objectID = this.objectID;
|
||||
|
@ -215,7 +215,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
|
||||
|
||||
// which method do we access?
|
||||
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass());
|
||||
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass(), invokeMethod.objectID);
|
||||
|
||||
for (int i = 0, n = cachedMethods.length; i < n; i++) {
|
||||
CachedMethod cachedMethod = cachedMethods[i];
|
||||
|
@ -255,10 +255,9 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
|
||||
byte responseID = (byte) 0;
|
||||
// An invocation doesn't need a response is if it's
|
||||
// VOID return type
|
||||
// ASYNC and no return values or exceptions are wanted back
|
||||
Class<?> returnType = method.getReturnType();
|
||||
boolean ignoreResponse = returnType == void.class || this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
|
||||
boolean ignoreResponse = this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
|
||||
if (ignoreResponse) {
|
||||
invokeMethod.responseData = (byte) 0; // 0 means do not respond.
|
||||
}
|
||||
|
@ -266,7 +265,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
synchronized (this) {
|
||||
// Increment the response counter and put it into the low bits of the responseID.
|
||||
responseID = this.nextResponseId++;
|
||||
if (this.nextResponseId > RmiImplHandler.responseIdMask) {
|
||||
if (this.nextResponseId > RmiBridge.responseIdMask) {
|
||||
this.nextResponseId = (byte) 1;
|
||||
}
|
||||
this.pendingResponses[responseID] = true;
|
||||
|
@ -274,15 +273,17 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
// Pack other data into the high bits.
|
||||
byte responseData = responseID;
|
||||
if (this.transmitReturnValue) {
|
||||
responseData |= (byte) RmiImplHandler.returnValueMask;
|
||||
responseData |= (byte) RmiBridge.returnValueMask;
|
||||
}
|
||||
if (this.transmitExceptions) {
|
||||
responseData |= (byte) RmiImplHandler.returnExceptionMask;
|
||||
responseData |= (byte) RmiBridge.returnExceptionMask;
|
||||
}
|
||||
invokeMethod.responseData = responseData;
|
||||
}
|
||||
|
||||
// Sends our invokeMethod to the remote connection, which the RmiImplHandler listens for
|
||||
this.lastResponseID = (byte) (invokeMethod.responseData & RmiBridge.responseIdMask);
|
||||
|
||||
// Sends our invokeMethod to the remote connection, which the RmiBridge listens for
|
||||
if (this.udp) {
|
||||
this.connection.send()
|
||||
.UDP(invokeMethod)
|
||||
|
@ -310,10 +311,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
"#" + method.getName() + "(" + argString + ")");
|
||||
}
|
||||
|
||||
this.lastResponseID = (byte) (invokeMethod.responseData & RmiImplHandler.responseIdMask);
|
||||
|
||||
// 0 means respond immediately because it's
|
||||
// VOID return type
|
||||
// ASYNC and no return values or exceptions are wanted back
|
||||
if (this.isAsync) {
|
||||
if (returnType.isPrimitive()) {
|
||||
|
@ -341,15 +339,9 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
if (returnType == double.class) {
|
||||
return 0.0d;
|
||||
}
|
||||
if (returnType == void.class) {
|
||||
return 0.0d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (returnType == void.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Object result = waitForResponse(this.lastResponseID);
|
||||
|
@ -375,11 +367,21 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
*/
|
||||
private
|
||||
Object waitForResponse(final byte responseID) throws IOException {
|
||||
long endTime = System.currentTimeMillis() + this.timeoutMillis;
|
||||
long remaining = this.timeoutMillis;
|
||||
// if timeout == 0, we wait "forever"
|
||||
long remaining;
|
||||
long endTime;
|
||||
|
||||
if (remaining == 0) {
|
||||
// just wait however log it takes.
|
||||
if (this.timeoutMillis != 0) {
|
||||
remaining = this.timeoutMillis;
|
||||
endTime = System.currentTimeMillis() + remaining;
|
||||
} else {
|
||||
// not forever, but close enough
|
||||
remaining = Long.MAX_VALUE;
|
||||
endTime = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
// wait for the specified time
|
||||
while (remaining > 0) {
|
||||
InvokeMethodResult invokeMethodResult;
|
||||
synchronized (this) {
|
||||
invokeMethodResult = this.responseTable[responseID];
|
||||
|
@ -392,7 +394,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
else {
|
||||
this.lock.lock();
|
||||
try {
|
||||
this.responseCondition.await();
|
||||
this.responseCondition.await(remaining, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread()
|
||||
.interrupt();
|
||||
|
@ -402,41 +404,7 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
invokeMethodResult = this.responseTable[responseID];
|
||||
}
|
||||
if (invokeMethodResult != null) {
|
||||
this.lastResponseID = null;
|
||||
return invokeMethodResult.result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// wait for the specified time
|
||||
while (remaining > 0) {
|
||||
InvokeMethodResult invokeMethodResult;
|
||||
synchronized (this) {
|
||||
invokeMethodResult = this.responseTable[responseID];
|
||||
}
|
||||
|
||||
if (invokeMethodResult != null) {
|
||||
this.lastResponseID = null;
|
||||
return invokeMethodResult.result;
|
||||
}
|
||||
else {
|
||||
this.lock.lock();
|
||||
try {
|
||||
this.responseCondition.await(remaining, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread()
|
||||
.interrupt();
|
||||
throw new IOException("Response timed out.", e);
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
remaining = endTime - System.currentTimeMillis();
|
||||
}
|
||||
remaining = endTime - System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// only get here if we timeout
|
||||
|
|
|
@ -68,6 +68,7 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once when the RMI bridge is created.
|
||||
*
|
||||
* @return true if there are classes that have been registered for RMI
|
||||
*/
|
||||
boolean initRmiSerialization();
|
||||
|
@ -82,6 +83,15 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
*/
|
||||
void returnKryo(KryoExtra kryo);
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
*
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
Class<?> getRmiIface(int objectId);
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
|
||||
*
|
||||
|
@ -91,6 +101,20 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
*/
|
||||
Class<?> getRmiImpl(int objectId);
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified interface
|
||||
*
|
||||
* @return the corresponding implementation
|
||||
*/
|
||||
Class<?> getRmiImpl(Class<?> iface);
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
Class<?> getRmiIface(Class<?> implementation);
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* <p/>
|
||||
|
@ -100,8 +124,6 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
*/
|
||||
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Objects that we want to use RMI with, must be accessed via an interface. This method configures the serialization of an
|
||||
* implementation to be serialized via the defined interface, as a RemoteObject (ie: proxy object). If the implementation class is
|
||||
|
|
|
@ -4,8 +4,8 @@ package dorkbox.network.rmi;
|
|||
*
|
||||
*/
|
||||
public
|
||||
class MessageWithTestObject implements RmiMessages {
|
||||
class MessageWithTestCow implements RmiMessages {
|
||||
public int number;
|
||||
public String text;
|
||||
public TestObject testObject;
|
||||
public TestCow testCow;
|
||||
}
|
|
@ -40,7 +40,6 @@ import static org.junit.Assert.assertTrue;
|
|||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -61,11 +60,11 @@ class RmiGlobalTest extends BaseTest {
|
|||
private int CLIENT_GLOBAL_OBJECT_ID = 0;
|
||||
private int SERVER_GLOBAL_OBJECT_ID = 0;
|
||||
|
||||
private final TestObject globalRemoteServerObject = new TestObjectImpl();
|
||||
private final TestObject globalRemoteClientObject = new TestObjectImpl();
|
||||
private final TestCow globalRemoteServerObject = new TestCowImpl();
|
||||
private final TestCow globalRemoteClientObject = new TestCowImpl();
|
||||
|
||||
private static
|
||||
void runTest(final Connection connection, final TestObject rObject, final TestObject test, final int remoteObjectID) {
|
||||
void runTest(final Connection connection, final TestCow rObject, final TestCow test, final int remoteObjectID) {
|
||||
System.err.println("Starting test for: " + remoteObjectID);
|
||||
|
||||
assertEquals(rObject.hashCode(), test.hashCode());
|
||||
|
@ -170,10 +169,10 @@ class RmiGlobalTest extends BaseTest {
|
|||
|
||||
|
||||
// Test sending a reference to a remote object.
|
||||
MessageWithTestObject m = new MessageWithTestObject();
|
||||
MessageWithTestCow m = new MessageWithTestCow();
|
||||
m.number = 678;
|
||||
m.text = "sometext";
|
||||
m.testObject = test;
|
||||
m.testCow = test;
|
||||
connection.send()
|
||||
.TCP(m)
|
||||
.flush();
|
||||
|
@ -184,10 +183,7 @@ class RmiGlobalTest extends BaseTest {
|
|||
public static
|
||||
void register(dorkbox.network.util.CryptoSerializationManager manager) {
|
||||
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
|
||||
|
||||
// manager.rmi().register(TestObject.class).override(TestObject.class, TestObjectImpl.class);
|
||||
manager.register(MessageWithTestObject.class);
|
||||
|
||||
manager.register(MessageWithTestCow.class);
|
||||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
|
@ -202,6 +198,12 @@ class RmiGlobalTest extends BaseTest {
|
|||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
|
@ -218,11 +220,20 @@ class RmiGlobalTest extends BaseTest {
|
|||
public
|
||||
void connected(final Connection connection) {
|
||||
try {
|
||||
connection.getRemoteObject(CLIENT_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestObject>() {
|
||||
connection.getRemoteObject(CLIENT_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestObject remoteObject) {
|
||||
runTest(connection, globalRemoteClientObject, remoteObject, CLIENT_GLOBAL_OBJECT_ID);
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Server -> Client");
|
||||
runTest(connection, globalRemoteClientObject, remoteObject, CLIENT_GLOBAL_OBJECT_ID);
|
||||
System.err.println("Done with test for: Server -> Client");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
|
@ -233,14 +244,17 @@ class RmiGlobalTest extends BaseTest {
|
|||
});
|
||||
|
||||
server.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Client/Server Finished!");
|
||||
assertEquals(SERVER_GLOBAL_OBJECT_ID, id);
|
||||
|
||||
System.err.println("Finished test for: Client -> Server");
|
||||
|
||||
stopEndPoints(2000);
|
||||
}
|
||||
|
@ -248,32 +262,59 @@ class RmiGlobalTest extends BaseTest {
|
|||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
|
||||
// register this object as a global object that the server will get
|
||||
CLIENT_GLOBAL_OBJECT_ID = client.createGlobalObject(globalRemoteClientObject);
|
||||
|
||||
addEndPoint(client);
|
||||
|
||||
client.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Server -> Client");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Server/Client Finished!");
|
||||
assertEquals(CLIENT_GLOBAL_OBJECT_ID, id);
|
||||
|
||||
System.err.println("Finished test for: Server -> Client");
|
||||
|
||||
// normally this is in the 'connected', but we do it here, so that it's more linear and easier to debug
|
||||
try {
|
||||
connection.getRemoteObject(SERVER_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestObject>() {
|
||||
connection.getRemoteObject(SERVER_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestObject remoteObject) {
|
||||
runTest(connection, globalRemoteServerObject, remoteObject, SERVER_GLOBAL_OBJECT_ID);
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Client -> Server");
|
||||
runTest(connection, globalRemoteServerObject, remoteObject, SERVER_GLOBAL_OBJECT_ID);
|
||||
System.err.println("Done with test for: Client -> Server");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
|
@ -287,21 +328,6 @@ class RmiGlobalTest extends BaseTest {
|
|||
waitForThreads();
|
||||
}
|
||||
|
||||
private
|
||||
interface TestObject extends Serializable {
|
||||
void throwException();
|
||||
|
||||
void moo();
|
||||
|
||||
void moo(String value);
|
||||
|
||||
void moo(String value, long delay);
|
||||
|
||||
int id();
|
||||
|
||||
float slow();
|
||||
}
|
||||
|
||||
private static
|
||||
class ConnectionAware {
|
||||
private
|
||||
|
@ -320,13 +346,13 @@ class RmiGlobalTest extends BaseTest {
|
|||
|
||||
|
||||
private static
|
||||
class TestObjectImpl extends ConnectionAware implements TestObject {
|
||||
class TestCowImpl extends ConnectionAware implements TestCow {
|
||||
public long value = System.currentTimeMillis();
|
||||
public int moos;
|
||||
private final int id = 1;
|
||||
|
||||
public
|
||||
TestObjectImpl() {
|
||||
TestCowImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -379,12 +405,4 @@ class RmiGlobalTest extends BaseTest {
|
|||
return 123.0F;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
class MessageWithTestObject implements RmiMessages {
|
||||
public int number;
|
||||
public String text;
|
||||
public TestObject testObject;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ class RmiTest extends BaseTest {
|
|||
|
||||
|
||||
// Test sending a reference to a remote object.
|
||||
MessageWithTestObject m = new MessageWithTestObject();
|
||||
MessageWithTestCow m = new MessageWithTestCow();
|
||||
m.number = 678;
|
||||
m.text = "sometext";
|
||||
m.testCow = test;
|
||||
|
@ -172,7 +172,7 @@ class RmiTest extends BaseTest {
|
|||
public static
|
||||
void register(dorkbox.network.util.CryptoSerializationManager manager) {
|
||||
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
|
||||
manager.register(MessageWithTestObject.class);
|
||||
manager.register(MessageWithTestCow.class);
|
||||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
|
@ -201,10 +201,10 @@ class RmiTest extends BaseTest {
|
|||
server.bind(false);
|
||||
|
||||
final ListenerBridge listeners = server.listeners();
|
||||
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
|
@ -296,10 +296,10 @@ class RmiTest extends BaseTest {
|
|||
});
|
||||
|
||||
client.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
|
|
|
@ -19,7 +19,7 @@ import dorkbox.network.connection.Connection;
|
|||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestObject;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
|
||||
/**
|
||||
|
@ -41,8 +41,8 @@ class TestClient
|
|||
|
||||
// rootLogger.setLevel(Level.OFF);
|
||||
|
||||
// rootLogger.setLevel(Level.DEBUG);
|
||||
rootLogger.setLevel(Level.TRACE);
|
||||
rootLogger.setLevel(Level.DEBUG);
|
||||
// rootLogger.setLevel(Level.TRACE);
|
||||
// rootLogger.setLevel(Level.ALL);
|
||||
|
||||
|
||||
|
@ -79,11 +79,13 @@ class TestClient
|
|||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
configuration.udtPort = 2002;
|
||||
configuration.host = "localhost";
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
|
||||
try {
|
||||
|
@ -99,16 +101,17 @@ class TestClient
|
|||
|
||||
try {
|
||||
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
|
||||
connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
|
||||
connection.getRemoteObject(TestCow.class, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestObject remoteObject) {
|
||||
void created(final TestCow remoteObject) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
RmiTest.runTests(connection, remoteObject, 1);
|
||||
System.err.println("DONE");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
@ -117,18 +120,6 @@ class TestClient
|
|||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
|
||||
// try {
|
||||
// connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
|
||||
// @Override
|
||||
// public
|
||||
// void created(final TestObject remoteObject) {
|
||||
// remoteObject.test();
|
||||
// }
|
||||
// });
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import java.io.IOException;
|
|||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestObject;
|
||||
import dorkbox.network.rmi.TestObjectImpl;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import dorkbox.network.rmi.TestCowImpl;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -22,10 +22,12 @@ class TestServer
|
|||
|
||||
dorkbox.network.Configuration configuration = new dorkbox.network.Configuration();
|
||||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
configuration.udtPort = 2002;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
Server server = null;
|
||||
try {
|
||||
|
@ -65,7 +67,7 @@ class TestServer
|
|||
// System.err.println("CONNECTED!");
|
||||
//
|
||||
// try {
|
||||
// TestObject object = connection.createProxyObject(TestObject.class);
|
||||
// TestCow object = connection.createProxyObject(TestCow.class);
|
||||
// object.test();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
|
|
Loading…
Reference in New Issue
Block a user