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