Fixed RMI, so now it will properly detect methods in parent
classes/interfaces, without having to register them explicitly. CachedMethods are no longer static, and there are fewer lookups
This commit is contained in:
parent
3e28b68e16
commit
33f8f0843e
|
@ -27,7 +27,17 @@ import io.netty.channel.socket.oio.OioDatagramChannel;
|
|||
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
||||
|
||||
/**
|
||||
* from: https://blog.cloudflare.com/how-the-consumer-product-safety-commission-is-inadvertently-behind-the-internets-largest-ddos-attacks/
|
||||
*
|
||||
* NOTE: CloudFlare has anti-DNS reflection protections in place. Specifically, we automatically upgrade from UDP to TCP when a DNS response
|
||||
* is particularly large (generally, over 512 bytes). Since TCP requires a handshake, it prevents source IP address spoofing which is
|
||||
* necessary for a DNS amplification attack.
|
||||
*
|
||||
* In addition, we rate limit unknown resolvers. Again, this helps ensure that our infrastructure can't be abused to amplify attacks.
|
||||
*
|
||||
* Finally, across our DNS infrastructure we have deprecated ANY queries and have proposed to the IETF to restrict ANY queries to only
|
||||
* authorized parties. By neutering ANY, we've significantly reduced the maximum size of responses even for zone files that need to be
|
||||
* large due to a large number of records.
|
||||
*/
|
||||
public
|
||||
class DnsServer extends EndPoint {
|
||||
|
|
|
@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.connection.idle.IdleSender;
|
||||
|
@ -44,7 +42,7 @@ import dorkbox.network.rmi.RemoteObjectCallback;
|
|||
import dorkbox.network.rmi.Rmi;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.network.rmi.RmiRegistration;
|
||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||
import dorkbox.network.serialization.RmiSerializationManager;
|
||||
import dorkbox.util.collections.IntMap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
|
@ -947,70 +945,93 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||
TCP(message).flush();
|
||||
}
|
||||
|
||||
private
|
||||
void collectRmiFields(final RmiBridge rmiBridge,
|
||||
final LinkedList<ClassObject> classesToCheck, final ClassObject remoteClassObject, final Field[] fields) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
final
|
||||
void registerInternal(final ConnectionImpl connection, final RmiRegistration remoteRegistration) {
|
||||
final Class<?> interfaceClass = remoteRegistration.interfaceClass;
|
||||
final int rmiID = remoteRegistration.rmiID;
|
||||
|
||||
if (interfaceClass != null) {
|
||||
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist)
|
||||
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation)
|
||||
//
|
||||
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
|
||||
Class<?> implementationClass;
|
||||
|
||||
// have to find the implementation from the specified interface
|
||||
CryptoSerializationManager manager = getEndPoint().serializationManager;
|
||||
KryoExtra kryo = manager.takeKryo();
|
||||
Registration registration = kryo.getRegistration(interfaceClass);
|
||||
// the interface class kryo ID == implementation class kryo ID, so they switcheroo automatically.
|
||||
final Class<?> implementationClass = interfaceClass;
|
||||
|
||||
final RmiSerializationManager manager = getEndPoint().serializationManager;
|
||||
|
||||
if (registration == null) {
|
||||
KryoExtra kryo = null;
|
||||
final Object remotePrimaryObject;
|
||||
try {
|
||||
kryo = manager.takeKryo();
|
||||
// this is what creates a new instance of the impl class, and stores it as an ID.
|
||||
remotePrimaryObject = kryo.newInstance(implementationClass);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error creating RMI class " + implementationClass, e);
|
||||
connection.TCP(new RmiRegistration(rmiID))
|
||||
.flush();
|
||||
return;
|
||||
} finally {
|
||||
if (kryo != null) {
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
logger.error("Error getting RMI class interface for " + interfaceClass);
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
implementationClass = manager.getRmiImpl(registration.getId());
|
||||
if (implementationClass == null) {
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
logger.error("Error getting RMI class implementation for " + interfaceClass);
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// this is what creates a new instance of the impl class, and stores it as an ID.
|
||||
final Object remotePrimaryObject = kryo.newInstance(implementationClass);
|
||||
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
rmiBridge.register(rmiBridge.nextObjectId(), remotePrimaryObject);
|
||||
|
||||
LinkedList<ClassObject> remoteClasses = new LinkedList<ClassObject>();
|
||||
remoteClasses.add(new ClassObject(implementationClass, remotePrimaryObject));
|
||||
|
||||
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI
|
||||
LinkedList<ClassObject> classesToCheck = new LinkedList<ClassObject>();
|
||||
classesToCheck.add(new ClassObject(implementationClass, remotePrimaryObject));
|
||||
|
||||
ClassObject remoteClassObject;
|
||||
while ((remoteClassObject = remoteClasses.pollFirst()) != null) {
|
||||
// we have to check for any additional fields that will have proxy information
|
||||
while (!classesToCheck.isEmpty()) {
|
||||
remoteClassObject = classesToCheck.removeFirst();
|
||||
|
||||
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information.
|
||||
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible.
|
||||
for (Field field : remoteClassObject.clazz.getDeclaredFields()) {
|
||||
if (field.getAnnotation(Rmi.class) != null) {
|
||||
boolean prev = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
final Object o = field.get(remoteClassObject.object);
|
||||
field.setAccessible(prev);
|
||||
|
||||
final Class<?> type = field.getType();
|
||||
|
||||
rmiBridge.register(rmiBridge.nextObjectId(), o);
|
||||
remoteClasses.offerLast(new ClassObject(type, o));
|
||||
if (!type.isInterface()) {
|
||||
// the type must be an interface, otherwise RMI cannot create a proxy object
|
||||
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!", remoteClassObject.clazz, field.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
boolean prev = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
final Object o;
|
||||
try {
|
||||
o = field.get(remoteClassObject.object);
|
||||
|
||||
rmiBridge.register(rmiBridge.nextObjectId(), o);
|
||||
classesToCheck.add(new ClassObject(type, o));
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.clazz, field.getName(), e);
|
||||
} finally {
|
||||
field.setAccessible(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// have to check the object hierarchy as well
|
||||
Class<?> superclass = remoteClassObject.clazz.getSuperclass();
|
||||
if (superclass != null && superclass != Object.class) {
|
||||
classesToCheck.add(new ClassObject(superclass, remoteClassObject.object));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1021,7 +1042,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||
}
|
||||
}
|
||||
else if (remoteRegistration.remoteObjectId > RmiBridge.INVALID_RMI) {
|
||||
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist)
|
||||
// THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist)
|
||||
//
|
||||
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
|
||||
Object object = getImplementationObject(remoteRegistration.remoteObjectId);
|
||||
|
@ -1057,8 +1078,8 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||
/**
|
||||
* Used by RMI
|
||||
*
|
||||
* @return the registered ID for a specific object. This is used by the "local" side when setting up the to fetch an object for the
|
||||
* "remote" side for RMI
|
||||
* @return the registered ID for a specific object. This is used by the "client" side when setting up the to fetch an object for the
|
||||
* "service" side for RMI
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
|
@ -1070,17 +1091,18 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
|||
throw new NullPointerException("Unable to call 'getRegisteredId' when the globalRmiBridge is null!");
|
||||
}
|
||||
|
||||
int object1 = globalRmiBridge.getRegisteredId(object);
|
||||
if (object1 == Integer.MAX_VALUE) {
|
||||
int objectId = globalRmiBridge.getRegisteredId(object);
|
||||
if (objectId == Integer.MAX_VALUE) {
|
||||
return rmiBridge.getRegisteredId(object);
|
||||
} else {
|
||||
return object1;
|
||||
return objectId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by RMI for the LOCAL side, to get the proxy object as an interface
|
||||
* Used by RMI for the CLIENT side, to get the proxy object as an interface
|
||||
*
|
||||
* @param objectID is the RMI object ID
|
||||
* @param type must be the interface the proxy will bind to
|
||||
*/
|
||||
@Override
|
||||
|
|
|
@ -36,7 +36,7 @@ import dorkbox.network.connection.wrapper.ChannelWrapper;
|
|||
import dorkbox.network.pipeline.KryoEncoder;
|
||||
import dorkbox.network.pipeline.KryoEncoderCrypto;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.network.store.NullSettingsStore;
|
||||
import dorkbox.network.store.SettingsStore;
|
||||
import dorkbox.util.Property;
|
||||
|
@ -135,7 +135,7 @@ class EndPointBase<C extends Connection> extends EndPoint {
|
|||
if (config.serialization != null) {
|
||||
serializationManager = config.serialization;
|
||||
} else {
|
||||
serializationManager = SerializationManager.DEFAULT();
|
||||
serializationManager = Serialization.DEFAULT();
|
||||
}
|
||||
|
||||
// setup our RMI serialization managers. Can only be called once
|
||||
|
|
|
@ -35,32 +35,18 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
import dorkbox.network.connection.Connection;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import dorkbox.network.connection.Connection;
|
||||
|
||||
public
|
||||
class AsmCachedMethod extends CachedMethod {
|
||||
public String name;
|
||||
public MethodAccess methodAccess;
|
||||
public int methodAccessIndex = -1;
|
||||
|
||||
@Override
|
||||
public
|
||||
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
if (origMethod == method) {
|
||||
Object invoke(final Connection connection, Object target, Object[] args) {
|
||||
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
|
||||
}
|
||||
else {
|
||||
int length = args.length;
|
||||
Object[] newArgs = new Object[length + 1];
|
||||
newArgs[0] = connection;
|
||||
System.arraycopy(args, 0, newArgs, 1, length);
|
||||
|
||||
return this.methodAccess.invoke(target, this.methodAccessIndex, newArgs);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new InvocationTargetException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,330 +34,17 @@
|
|||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import com.esotericsoftware.kryo.util.Util;
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPointBase;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.serialization.CryptoSerializationManager;
|
||||
import dorkbox.network.serialization.RmiSerializationManager;
|
||||
import dorkbox.util.ClassHelper;
|
||||
|
||||
/**
|
||||
* This class is NOT sent across the wire
|
||||
*/
|
||||
public
|
||||
class CachedMethod {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CachedMethod.class);
|
||||
|
||||
private static final Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
|
||||
@Override
|
||||
public
|
||||
int compare(Method o1, Method o2) {
|
||||
// Methods are sorted so they can be represented as an index.
|
||||
String o1Name = o1.getName();
|
||||
String o2Name = o2.getName();
|
||||
|
||||
|
||||
int diff = o1Name.compareTo(o2Name);
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
Class<?>[] argTypes1 = o1.getParameterTypes();
|
||||
Class<?>[] argTypes2 = o2.getParameterTypes();
|
||||
if (argTypes1.length > argTypes2.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argTypes1.length < argTypes2.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < argTypes1.length; i++) {
|
||||
diff = argTypes1[i].getName()
|
||||
.compareTo(argTypes2[i].getName());
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Impossible, should never happen
|
||||
throw new RuntimeException("Two methods with same signature! ('" + o1Name + "', '" + o2Name + "'");
|
||||
}
|
||||
};
|
||||
|
||||
// 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[]>(EndPointBase.DEFAULT_THREAD_POOL_SIZE);
|
||||
|
||||
|
||||
/**
|
||||
* 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, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
final KryoExtra kryo = serializationManager.takeKryo();
|
||||
try {
|
||||
cachedMethods = getCachedMethods(kryo, type, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
} finally {
|
||||
serializationManager.returnKryo(kryo);
|
||||
}
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
// race-conditions are OK, because we just recreate the same thing.
|
||||
private static
|
||||
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];
|
||||
|
||||
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
// the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
// the list of parameters.
|
||||
//
|
||||
// for example:
|
||||
// Interface: foo(String x)
|
||||
// Impl: foo(Connection c, String x)
|
||||
//
|
||||
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
|
||||
// This MUST hold valid for both remote and local connection types.
|
||||
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
if (asmEnabled && type != Object.class && !Util.isAndroid && Modifier.isPublic(type.getModifiers())) {
|
||||
methodAccess = MethodAccess.get(type);
|
||||
if (methodAccess.getMethodNames().length == 0 && methodAccess.getParameterTypes().length == 0 &&
|
||||
methodAccess.getReturnTypes().length == 0) {
|
||||
|
||||
// there was NOTHING reflectASM found, so trying to use it doesn't do us any good
|
||||
methodAccess = null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (overriddenMethods != null) {
|
||||
Method overriddenMethod = overriddenMethods.remove(method);
|
||||
if (overriddenMethod != null) {
|
||||
// we can override the details of this method BECAUSE (and only because) our kryo registration override will return
|
||||
// the correct object for this overridden method to be called on.
|
||||
method = overriddenMethod;
|
||||
|
||||
Class<?> overrideType = declaringClass;
|
||||
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
|
||||
localMethodAccess = MethodAccess.get(overrideType);
|
||||
asmParameterTypes = method.getParameterTypes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CachedMethod cachedMethod = null;
|
||||
if (localMethodAccess != null) {
|
||||
try {
|
||||
final int index = localMethodAccess.getIndex(method.getName(), asmParameterTypes);
|
||||
|
||||
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
|
||||
asmCachedMethod.methodAccessIndex = index;
|
||||
asmCachedMethod.methodAccess = localMethodAccess;
|
||||
cachedMethod = asmCachedMethod;
|
||||
} catch (Exception e) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, method.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedMethod == null) {
|
||||
cachedMethod = new CachedMethod();
|
||||
}
|
||||
cachedMethod.method = method;
|
||||
cachedMethod.origMethod = origMethod;
|
||||
|
||||
|
||||
// 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.
|
||||
// ONLY for the ORIGINAL method, not he overridden one.
|
||||
cachedMethod.serializers = new Serializer<?>[parameterTypes.length];
|
||||
for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
|
||||
if (kryo.isFinal(parameterTypes[ii])) {
|
||||
cachedMethod.serializers[ii] = kryo.getSerializer(parameterTypes[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
cachedMethods[i] = cachedMethod;
|
||||
}
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
// does not null check
|
||||
private static
|
||||
IdentityMap<Method, Method> getOverriddenMethods(final Class<?> type, final ArrayList<Method> origMethods) {
|
||||
final ArrayList<Method> implMethods = getMethods(type);
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overrideMap;
|
||||
}
|
||||
|
||||
private static
|
||||
ArrayList<Method> getMethods(final Class<?> type) {
|
||||
final ArrayList<Method> allMethods = new ArrayList<Method>();
|
||||
|
||||
Class<?> nextClass = type;
|
||||
while (nextClass != null) {
|
||||
Collections.addAll(allMethods, nextClass.getDeclaredMethods());
|
||||
nextClass = nextClass.getSuperclass();
|
||||
if (nextClass == Object.class) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final ArrayList<Method> methods = new ArrayList<Method>(Math.max(1, allMethods.size()));
|
||||
for (int i = 0, n = allMethods.size(); i < n; i++) {
|
||||
Method method = allMethods.get(i);
|
||||
int modifiers = method.getModifiers();
|
||||
if (Modifier.isStatic(modifiers)) {
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isPrivate(modifiers)) {
|
||||
continue;
|
||||
}
|
||||
if (method.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
methods.add(method);
|
||||
}
|
||||
|
||||
Collections.sort(methods, METHOD_COMPARATOR);
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
public Method method;
|
||||
public int methodClassID;
|
||||
public int methodIndex;
|
||||
|
@ -366,24 +53,13 @@ class CachedMethod {
|
|||
* in some cases, we want to override the cached method, with one that supports passing 'Connection' as the first argument. This is
|
||||
* completely OPTIONAL, however - greatly adds functionality to RMI methods.
|
||||
*/
|
||||
public transient Method origMethod;
|
||||
public boolean overriddenMethod;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Serializer[] serializers;
|
||||
|
||||
public
|
||||
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
|
||||
// did we override our cached method?
|
||||
if (method == origMethod) {
|
||||
Object invoke(final Connection connection, Object target, Object[] args) throws Exception {
|
||||
return this.method.invoke(target, args);
|
||||
}
|
||||
else {
|
||||
int length = args.length;
|
||||
Object[] newArgs = new Object[length + 1];
|
||||
newArgs[0] = connection;
|
||||
System.arraycopy(args, 0, newArgs, 1, length);
|
||||
|
||||
return this.method.invoke(target, newArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ package dorkbox.network.rmi;
|
|||
*/
|
||||
public
|
||||
class InvokeMethod implements RmiMessages {
|
||||
public int objectID;
|
||||
public int objectID; // the registered kryo ID for the object
|
||||
public CachedMethod cachedMethod;
|
||||
public Object[] args;
|
||||
|
||||
|
|
|
@ -40,6 +40,8 @@ import com.esotericsoftware.kryo.Serializer;
|
|||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
/**
|
||||
* Internal message to invoke methods remotely.
|
||||
*/
|
||||
|
@ -84,38 +86,60 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
@Override
|
||||
public
|
||||
InvokeMethod read(final Kryo kryo, final Input input, final Class<InvokeMethod> type) {
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
|
||||
invokeMethod.objectID = input.readInt(true);
|
||||
|
||||
int objectID = input.readInt(true);
|
||||
int methodClassID = input.readInt(true);
|
||||
Class<?> methodClass = kryo.getRegistration(methodClassID)
|
||||
.getType();
|
||||
|
||||
byte methodIndex = input.readByte();
|
||||
|
||||
// System.err.println(":: objectID " + objectID);
|
||||
// System.err.println(":: methodClassID " + methodClassID);
|
||||
// System.err.println(":: methodIndex " + methodIndex);
|
||||
|
||||
CachedMethod cachedMethod;
|
||||
try {
|
||||
cachedMethod = CachedMethod.getMethods(kryo, methodClass, methodClassID)[methodIndex];
|
||||
invokeMethod.cachedMethod = cachedMethod;
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
cachedMethod = ((KryoExtra) kryo).getSerializationManager().getMethods(methodClassID)[methodIndex];
|
||||
} catch (Exception ex) {
|
||||
Class<?> methodClass = kryo.getRegistration(methodClassID)
|
||||
.getType();
|
||||
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
|
||||
}
|
||||
|
||||
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) {
|
||||
args[i] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
|
||||
Object[] args;
|
||||
Serializer<?>[] serializers = cachedMethod.serializers;
|
||||
|
||||
int argStartIndex;
|
||||
|
||||
if (cachedMethod.overriddenMethod) {
|
||||
// did we override our cached method? This is not common.
|
||||
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
|
||||
argStartIndex = 1;
|
||||
|
||||
args = new Object[serializers.length + 1];
|
||||
args[0] = ((KryoExtra) kryo).connection;
|
||||
}
|
||||
else {
|
||||
args[i] = kryo.readClassAndObject(input);
|
||||
argStartIndex = 0;
|
||||
args = new Object[serializers.length];
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
|
||||
|
||||
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
|
||||
if (serializer != null) {
|
||||
args[j] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
|
||||
}
|
||||
else {
|
||||
args[j] = kryo.readClassAndObject(input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
invokeMethod.objectID = objectID;
|
||||
invokeMethod.cachedMethod = cachedMethod;
|
||||
invokeMethod.args = args;
|
||||
invokeMethod.responseData = input.readByte();
|
||||
|
||||
return invokeMethod;
|
||||
|
|
|
@ -24,16 +24,9 @@ import java.lang.annotation.Target;
|
|||
/**
|
||||
* This specifies to the serializer, that this class contains an RMI object, and that a specific field is an RMI object. Both are
|
||||
* necessary.
|
||||
* <p/>
|
||||
* Additional behavior of RMI methods, is if there is another method (of the same name and signature), with the addition of a Connection
|
||||
* parameter in the first position, THAT method will be called instead, an will have the current connection object passed into the method.
|
||||
* <p/>
|
||||
* It is mandatory for the correct implementation (as per the interface guideline) to exist, and should return null.
|
||||
* <p/>
|
||||
* IE: foo(String something)... -> foo(Connection connection, String something)....
|
||||
*/
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Target(value = {ElementType.TYPE, ElementType.FIELD})
|
||||
@Target(value = {ElementType.FIELD})
|
||||
public
|
||||
@interface Rmi {}
|
||||
|
|
|
@ -68,21 +68,18 @@ import dorkbox.util.collections.ObjectIntMap;
|
|||
* <p/>
|
||||
* <p/>
|
||||
* In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||
* <p/>
|
||||
* This is to support calling RMI methods from an interface (that does pass the connection reference) to an implementation, that DOES pass
|
||||
* the connection reference. The remote side (that initiates the RMI calls), MUST use the interface, and the implementation may override the
|
||||
* method, so that we add the connection as the first in the list of parameters.
|
||||
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
* the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
* the list of parameters.
|
||||
* <p/>
|
||||
* for example:
|
||||
* Interface: foo(String x)
|
||||
* Impl: foo(Connection c, String x)
|
||||
* <p/>
|
||||
* The implementation (if it exists, with the same name, and with the same signature+connection) will be called from the interface. This
|
||||
* MUST hold valid for both remote and local connection types.
|
||||
*
|
||||
* 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 CachedMethod.registerOverridden(ifaceClass, implClass) must be called.
|
||||
*
|
||||
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
|
||||
* instead of the method that would NORMALLY be called.
|
||||
* <p/>
|
||||
*
|
||||
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
|
||||
*/
|
||||
|
@ -199,6 +196,8 @@ class RmiBridge {
|
|||
* 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 EndPointBase} for this RmiBridge and unless an executor has been set.
|
||||
*
|
||||
* This is the response to the invoke method in the RmiProxyHandler
|
||||
*
|
||||
* @param connection
|
||||
* The remote side of this connection requested the invocation.
|
||||
*/
|
||||
|
@ -229,7 +228,8 @@ class RmiBridge {
|
|||
.append(argString)
|
||||
.append(")");
|
||||
|
||||
if (cachedMethod.origMethod != null) {
|
||||
if (cachedMethod.overriddenMethod) {
|
||||
// did we override our cached method? This is not common.
|
||||
stringBuilder.append(" [Connection method override]");
|
||||
}
|
||||
logger2.trace(stringBuilder.toString());
|
||||
|
@ -259,8 +259,10 @@ class RmiBridge {
|
|||
result = cause;
|
||||
}
|
||||
else {
|
||||
throw new IOException("Error invoking method: " + cachedMethod.method.getDeclaringClass()
|
||||
.getName() + "." + cachedMethod.method.getName(), ex);
|
||||
String message = "Error invoking method: " + cachedMethod.method.getDeclaringClass()
|
||||
.getName() + "." + cachedMethod.method.getName();
|
||||
logger.error(message, ex);
|
||||
throw new IOException(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,6 +441,9 @@ class RmiBridge {
|
|||
if (iface == null) {
|
||||
throw new IllegalArgumentException("iface cannot be null.");
|
||||
}
|
||||
if (!iface.isInterface()) {
|
||||
throw new IllegalArgumentException("iface must be an interface.");
|
||||
}
|
||||
|
||||
Class<?>[] temp = new Class<?>[2];
|
||||
temp[0] = RemoteObject.class;
|
||||
|
@ -446,6 +451,6 @@ class RmiBridge {
|
|||
|
||||
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
|
||||
temp,
|
||||
new RmiProxyHandler(connection, objectID));
|
||||
new RmiProxyHandler(connection, objectID, iface));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,10 +48,13 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPointBase;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.serialization.RmiSerializationManager;
|
||||
|
||||
/**
|
||||
* Handles network communication when methods are invoked on a proxy.
|
||||
* <p>
|
||||
* The only methods than can be invoked are INTERFACE methods and OBJECT methods
|
||||
*/
|
||||
class RmiProxyHandler implements InvocationHandler {
|
||||
private final Logger logger;
|
||||
|
@ -63,7 +66,11 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
private final boolean[] pendingResponses = new boolean[64];
|
||||
|
||||
private final Connection connection;
|
||||
public final int objectID;
|
||||
private final Class<?> iFace;
|
||||
public final int objectID; // this is the RMI id
|
||||
public final int ID; // this is the KRYO id
|
||||
|
||||
|
||||
private final String proxyString;
|
||||
private final RemoteInvocationResponse<Connection> responseListener;
|
||||
|
||||
|
@ -80,14 +87,34 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
private Byte lastResponseID;
|
||||
private byte nextResponseId = (byte) 1;
|
||||
|
||||
RmiProxyHandler(final Connection connection, final int objectID) {
|
||||
/**
|
||||
* @param connection this is really the network client -- there is ONLY ever 1 connection
|
||||
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
|
||||
* @param iFace this is the RMI interface
|
||||
*/
|
||||
RmiProxyHandler(final Connection connection, final int objectID, final Class<?> iFace) {
|
||||
super();
|
||||
|
||||
this.connection = connection;
|
||||
this.iFace = iFace;
|
||||
this.objectID = objectID;
|
||||
this.proxyString = "<proxy #" + objectID + ">";
|
||||
|
||||
logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
|
||||
EndPointBase endPointBaseConnection = this.connection.getEndPoint();
|
||||
final RmiSerializationManager serializationManager = endPointBaseConnection.getSerialization();
|
||||
|
||||
KryoExtra kryoExtra = null;
|
||||
try {
|
||||
kryoExtra = serializationManager.takeKryo();
|
||||
this.ID = kryoExtra.getRegistration(iFace)
|
||||
.getId();
|
||||
} finally {
|
||||
if (kryoExtra != null) {
|
||||
serializationManager.returnKryo(kryoExtra);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
|
||||
|
||||
|
||||
this.responseListener = new RemoteInvocationResponse<Connection>() {
|
||||
|
@ -195,39 +222,18 @@ class RmiProxyHandler implements InvocationHandler {
|
|||
return proxyString;
|
||||
}
|
||||
|
||||
EndPointBase endPointBaseConnection = this.connection.getEndPoint();
|
||||
final RmiSerializationManager serializationManager = endPointBaseConnection.getSerialization();
|
||||
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
invokeMethod.objectID = this.objectID;
|
||||
invokeMethod.args = args;
|
||||
|
||||
|
||||
// which method do we access?
|
||||
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass(), invokeMethod.objectID);
|
||||
CachedMethod[] cachedMethods = connection.getEndPoint()
|
||||
.getSerialization()
|
||||
.getMethods(ID);
|
||||
|
||||
for (int i = 0, n = cachedMethods.length; i < n; i++) {
|
||||
CachedMethod cachedMethod = cachedMethods[i];
|
||||
Method checkMethod = cachedMethod.origMethod;
|
||||
if (checkMethod == null) {
|
||||
checkMethod = cachedMethod.method;
|
||||
}
|
||||
|
||||
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
// the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
// the list of parameters.
|
||||
//
|
||||
// for example:
|
||||
// Interface: foo(String x)
|
||||
// Impl: foo(Connection c, String x)
|
||||
//
|
||||
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
|
||||
// This MUST hold valid for both remote and local connection types.
|
||||
|
||||
// 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.
|
||||
Method checkMethod = cachedMethod.method;
|
||||
|
||||
if (checkMethod.equals(method)) {
|
||||
invokeMethod.cachedMethod = cachedMethod;
|
||||
|
|
367
src/dorkbox/network/rmi/RmiUtils.java
Normal file
367
src/dorkbox/network/rmi/RmiUtils.java
Normal file
|
@ -0,0 +1,367 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.util.ClassHelper;
|
||||
|
||||
/**
|
||||
* Utility methods for creating a method cache for a class or interface.
|
||||
*
|
||||
* Additionally, this will override methods on the implementation so that methods can be called with a {@link Connection} parameter as the
|
||||
* first parameter, with all other parameters being equal to the interface.
|
||||
*
|
||||
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
* the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
* the list of parameters.
|
||||
*
|
||||
* for example:
|
||||
* Interface: foo(String x)
|
||||
*
|
||||
* Impl: foo(String x) -> not called
|
||||
* Impl: foo(Connection c, String x) -> this is called instead
|
||||
*
|
||||
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
|
||||
* instead of the method that would NORMALLY be called.
|
||||
*/
|
||||
public
|
||||
class RmiUtils {
|
||||
private static final Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
|
||||
@Override
|
||||
public
|
||||
int compare(Method o1, Method o2) {
|
||||
// Methods are sorted so they can be represented as an index.
|
||||
String o1Name = o1.getName();
|
||||
String o2Name = o2.getName();
|
||||
|
||||
|
||||
int diff = o1Name.compareTo(o2Name);
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
Class<?>[] argTypes1 = o1.getParameterTypes();
|
||||
Class<?>[] argTypes2 = o2.getParameterTypes();
|
||||
if (argTypes1.length > argTypes2.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argTypes1.length < argTypes2.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < argTypes1.length; i++) {
|
||||
diff = argTypes1[i].getName()
|
||||
.compareTo(argTypes2[i].getName());
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Impossible, should never happen
|
||||
// return 0;
|
||||
throw new RuntimeException("Two methods with same signature! ('" + o1Name + "', '" + o2Name + "'");
|
||||
}
|
||||
};
|
||||
|
||||
private static
|
||||
MethodAccess getReflectAsmMethod(final Logger logger, final Class<?> clazz) {
|
||||
try {
|
||||
MethodAccess methodAccess = MethodAccess.get(clazz);
|
||||
if (methodAccess.getMethodNames().length == 0 &&
|
||||
methodAccess.getParameterTypes().length == 0 &&
|
||||
methodAccess.getReturnTypes().length == 0) {
|
||||
|
||||
// there was NOTHING that reflectASM found, so trying to use it doesn't do us any good
|
||||
return null;
|
||||
}
|
||||
|
||||
return methodAccess;
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create ReflectASM method access", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
CachedMethod[] getCachedMethods(final Logger logger, final Kryo kryo, final boolean asmEnabled, final Class<?> iFace, final Class<?> impl, final int classId) {
|
||||
MethodAccess ifaceMethodAccess = null;
|
||||
MethodAccess implMethodAccess = null;
|
||||
|
||||
// RMI is **ALWAYS** based upon an interface, so we must always make sure to get the methods of the interface, instead of the
|
||||
// implementation, otherwise we will have the wrong order of methods, so invoking a method by it's index will fail.
|
||||
|
||||
final Method[] methods = getMethods(iFace);
|
||||
final int size = methods.length;
|
||||
final CachedMethod[] cachedMethods = new CachedMethod[size];
|
||||
|
||||
if (impl != null) {
|
||||
if (impl.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot have type as an interface, it must be an implementation");
|
||||
}
|
||||
|
||||
final Method[] implMethods = getMethods(impl);
|
||||
overwriteMethodsWithConnectionParam(implMethods, methods);
|
||||
|
||||
|
||||
// reflectASM
|
||||
// doesn't work on android (set correctly by the serialization manager)
|
||||
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
|
||||
// and it MUST be public (iFace is always public)
|
||||
if (asmEnabled) {
|
||||
implMethodAccess = getReflectAsmMethod(logger, impl);
|
||||
}
|
||||
}
|
||||
|
||||
// reflectASM
|
||||
// doesn't work on android (set correctly by the serialization manager)
|
||||
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
|
||||
// and it MUST be public (iFace is always public)
|
||||
if (asmEnabled) {
|
||||
ifaceMethodAccess = getReflectAsmMethod(logger, iFace);
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
final Method method = methods[i];
|
||||
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
// copy because they can be overridden
|
||||
boolean overriddenMethod = false;
|
||||
Method tweakMethod = method;
|
||||
MethodAccess tweakMethodAccess = ifaceMethodAccess;
|
||||
|
||||
// this is how we detect if the method has been changed from the interface -> implementation + connection parameter
|
||||
if (declaringClass.equals(impl)) {
|
||||
tweakMethodAccess = implMethodAccess;
|
||||
overriddenMethod = true;
|
||||
|
||||
if (logger.isTraceEnabled())
|
||||
|
||||
logger.trace("Overridden method: {}.{}", impl, method.getName());
|
||||
}
|
||||
|
||||
CachedMethod cachedMethod = null;
|
||||
if (tweakMethodAccess != null) {
|
||||
try {
|
||||
final int index = tweakMethodAccess.getIndex(tweakMethod.getName(), parameterTypes);
|
||||
|
||||
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
|
||||
asmCachedMethod.methodAccessIndex = index;
|
||||
asmCachedMethod.methodAccess = tweakMethodAccess;
|
||||
asmCachedMethod.name = tweakMethod.getName();
|
||||
|
||||
if (overriddenMethod) {
|
||||
// logger.error(tweakMethod.getName() + " " + Arrays.toString(parameterTypes) + " index: " + index +
|
||||
// " methodIndex: " + i + " classID: " + classId);
|
||||
|
||||
// This is because we have to store the serializer for each parameter, but ONLY for the ORIGINAL method, not the overridden one.
|
||||
// this gets our parameters "back to the original" method. We do this to minimize the overhead of sending the args over
|
||||
int length = parameterTypes.length;
|
||||
if (length == 1) {
|
||||
parameterTypes = new Class<?>[0];
|
||||
}
|
||||
else {
|
||||
length--;
|
||||
Class<?>[] newArgs = new Class<?>[length];
|
||||
System.arraycopy(parameterTypes, 1, newArgs, 0, length);
|
||||
parameterTypes = newArgs;
|
||||
}
|
||||
}
|
||||
|
||||
cachedMethod = asmCachedMethod;
|
||||
} catch (Exception e) {
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, tweakMethod.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedMethod == null) {
|
||||
cachedMethod = new CachedMethod();
|
||||
}
|
||||
|
||||
cachedMethod.overriddenMethod = overriddenMethod;
|
||||
cachedMethod.methodClassID = classId;
|
||||
|
||||
// we ALSO have to setup "normal" reflection access to these methods
|
||||
cachedMethod.method = tweakMethod;
|
||||
cachedMethod.methodIndex = i;
|
||||
|
||||
// Store the serializer for each final parameter.
|
||||
// ONLY for the ORIGINAL method, not the overridden one.
|
||||
cachedMethod.serializers = new Serializer<?>[parameterTypes.length];
|
||||
for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
|
||||
if (kryo.isFinal(parameterTypes[ii])) {
|
||||
cachedMethod.serializers[ii] = kryo.getSerializer(parameterTypes[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
cachedMethods[i] = cachedMethod;
|
||||
}
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
// NOTE: does not null check
|
||||
/**
|
||||
* This will overwrite an original (iface based) method with a method from the implementation ONLY if there is the extra 'Connection' parameter (as per above)
|
||||
*
|
||||
* @param implMethods methods from the implementation
|
||||
* @param origMethods methods from the interface
|
||||
*/
|
||||
private static
|
||||
void overwriteMethodsWithConnectionParam(final Method[] implMethods, final Method[] origMethods) {
|
||||
for (int i = 0, origMethodsSize = origMethods.length; i < origMethodsSize; i++) {
|
||||
final Method origMethod = origMethods[i];
|
||||
|
||||
String origName = origMethod.getName();
|
||||
Class<?>[] origTypes = origMethod.getParameterTypes();
|
||||
int origLength = origTypes.length + 1;
|
||||
|
||||
for (Method implMethod : implMethods) {
|
||||
String implName = implMethod.getName();
|
||||
Class<?>[] implTypes = implMethod.getParameterTypes();
|
||||
int implLength = implTypes.length;
|
||||
|
||||
if (origLength != implLength || !(origName.equals(implName))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// checkLength > 0
|
||||
Class<?> shouldBeConnectionType = implTypes[0];
|
||||
if (ClassHelper.hasInterface(Connection.class, shouldBeConnectionType)) {
|
||||
// now we check to see if our "check" method is equal to our "cached" method + Connection
|
||||
if (implLength == 1) {
|
||||
// we only have "Connection" as a parameter
|
||||
origMethods[i] = implMethod;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
boolean found = true;
|
||||
for (int k = 1; k < implLength; k++) {
|
||||
if (origTypes[k - 1] != implTypes[k]) {
|
||||
// make sure all the parameters match. Cannot use arrays.equals(*), because one will have "Connection" as
|
||||
// a parameter - so we check that the rest match
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
origMethods[i] = implMethod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will methods from an interface (for RMI), and from an implementation (for "connection" overriding the method signature).
|
||||
*
|
||||
* @return an array list of all found methods for this class
|
||||
*/
|
||||
private static
|
||||
Method[] getMethods(final Class<?> type) {
|
||||
final ArrayList<Method> allMethods = new ArrayList<Method>();
|
||||
final Map<String, ArrayList<Class[]>> accessibleMethods = new HashMap<String, ArrayList<Class[]>>();
|
||||
|
||||
LinkedList<Class<?>> classes = new LinkedList<Class<?>>();
|
||||
classes.add(type);
|
||||
|
||||
// explicitly add Object.class because that can always be called, because it is common to 100% of all java objects (and it's methods
|
||||
// are not added via parentClass.getMethods()
|
||||
classes.add(Object.class);
|
||||
|
||||
Class<?> nextClass;
|
||||
while (!classes.isEmpty()) {
|
||||
nextClass = classes.removeFirst();
|
||||
|
||||
Method[] methods = nextClass.getMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
final Method method = methods[i];
|
||||
|
||||
// static and private methods cannot be called via RMI.
|
||||
int modifiers = method.getModifiers();
|
||||
if (Modifier.isStatic(modifiers)) {
|
||||
continue;
|
||||
}
|
||||
if (Modifier.isPrivate(modifiers)) {
|
||||
continue;
|
||||
}
|
||||
if (method.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// methods that have been over-ridden by another method cannot be called.
|
||||
// the first one in the map, is the "highest" level method, and is what can be called.
|
||||
String name = method.getName();
|
||||
Class<?>[] types = method.getParameterTypes(); // length 0 if there are no parameters
|
||||
|
||||
ArrayList<Class[]> existingTypes = accessibleMethods.get(name);
|
||||
if (existingTypes != null) {
|
||||
boolean found = false;
|
||||
for (Class[] existingType : existingTypes) {
|
||||
if (Arrays.equals(types, existingType)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// the method is overridden, so it should not be called.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingTypes == null) {
|
||||
existingTypes = new ArrayList<Class[]>();
|
||||
}
|
||||
existingTypes.add(types);
|
||||
|
||||
// add to the map for checking later
|
||||
accessibleMethods.put(name, existingTypes);
|
||||
|
||||
// safe to add this method to the list of recognized methods
|
||||
allMethods.add(method);
|
||||
}
|
||||
|
||||
// add all interfaces from our class (if any)
|
||||
classes.addAll(Arrays.asList(nextClass.getInterfaces()));
|
||||
|
||||
// If we are an interface, one CANNOT call any methods NOT defined by the interface!
|
||||
// also, interfaces don't have a super-class.
|
||||
Class<?> superclass = nextClass.getSuperclass();
|
||||
if (superclass != null) {
|
||||
classes.add(superclass);
|
||||
}
|
||||
}
|
||||
|
||||
accessibleMethods.clear();
|
||||
|
||||
Collections.sort(allMethods, METHOD_COMPARATOR);
|
||||
Method[] methodsArray = new Method[allMethods.size()];
|
||||
|
||||
allMethods.toArray(methodsArray);
|
||||
|
||||
return methodsArray;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import com.esotericsoftware.kryo.KryoException;
|
|||
import com.esotericsoftware.kryo.Serializer;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.rmi.CachedMethod;
|
||||
import dorkbox.util.SerializationManager;
|
||||
|
||||
public
|
||||
|
@ -94,31 +95,6 @@ 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)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
*
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -151,4 +127,9 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
* @throws IllegalArgumentException if the iface/impl have previously been overridden
|
||||
*/
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass);
|
||||
|
||||
/**
|
||||
* Gets the cached methods for the specified class ID
|
||||
*/
|
||||
CachedMethod[] getMethods(int classID);
|
||||
}
|
||||
|
|
|
@ -16,11 +16,10 @@
|
|||
package dorkbox.network.serialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
|
@ -29,6 +28,7 @@ import org.bouncycastle.crypto.params.IESParameters;
|
|||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.esotericsoftware.kryo.ClassResolver;
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
|
@ -41,10 +41,12 @@ import com.esotericsoftware.kryo.serializers.FieldSerializer;
|
|||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import com.esotericsoftware.kryo.util.IntMap;
|
||||
import com.esotericsoftware.kryo.util.MapReferenceResolver;
|
||||
import com.esotericsoftware.kryo.util.Util;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.connection.ping.PingMessage;
|
||||
import dorkbox.network.rmi.CachedMethod;
|
||||
import dorkbox.network.rmi.InvocationHandlerSerializer;
|
||||
import dorkbox.network.rmi.InvocationResultSerializer;
|
||||
import dorkbox.network.rmi.InvokeMethod;
|
||||
|
@ -52,28 +54,30 @@ import dorkbox.network.rmi.InvokeMethodResult;
|
|||
import dorkbox.network.rmi.InvokeMethodSerializer;
|
||||
import dorkbox.network.rmi.RemoteObjectSerializer;
|
||||
import dorkbox.network.rmi.RmiRegistration;
|
||||
import dorkbox.network.rmi.RmiUtils;
|
||||
import dorkbox.objectPool.ObjectPool;
|
||||
import dorkbox.objectPool.PoolableObject;
|
||||
import dorkbox.util.Property;
|
||||
import dorkbox.util.serialization.ArraysAsListSerializer;
|
||||
import dorkbox.util.serialization.EccPrivateKeySerializer;
|
||||
import dorkbox.util.serialization.EccPublicKeySerializer;
|
||||
import dorkbox.util.serialization.FieldAnnotationAwareSerializer;
|
||||
import dorkbox.util.serialization.IesParametersSerializer;
|
||||
import dorkbox.util.serialization.IesWithCipherParametersSerializer;
|
||||
import dorkbox.util.serialization.IgnoreSerialization;
|
||||
import dorkbox.util.serialization.UnmodifiableCollectionsSerializer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Threads reading/writing, it messes up a single instance. it is possible to use a single kryo with the use of synchronize, however - that
|
||||
* defeats the point of having multi-threaded serialization
|
||||
* defeats the point of having multi-threaded serialization.
|
||||
* <p>
|
||||
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
|
||||
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "StaticNonFinalField"})
|
||||
public
|
||||
class SerializationManager implements CryptoSerializationManager, RmiSerializationManager {
|
||||
class Serialization implements CryptoSerializationManager, RmiSerializationManager {
|
||||
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(SerializationManager.class);
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Serialization.class.getSimpleName());
|
||||
|
||||
/**
|
||||
* Specify if we want KRYO to use unsafe memory for serialization, or to use the ASM backend. Unsafe memory use is WAY faster, but is
|
||||
|
@ -82,49 +86,9 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
@Property
|
||||
public static boolean useUnsafeMemory = false;
|
||||
|
||||
public static
|
||||
SerializationManager DEFAULT() {
|
||||
return DEFAULT(true, true, true);
|
||||
}
|
||||
|
||||
public static
|
||||
SerializationManager DEFAULT(final boolean references, final boolean registrationRequired, final boolean forbidInterfaceRegistration) {
|
||||
// ignore fields that have the "@IgnoreSerialization" annotation.
|
||||
Collection<Class<? extends Annotation>> marks = new ArrayList<Class<? extends Annotation>>();
|
||||
marks.add(IgnoreSerialization.class);
|
||||
SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
|
||||
|
||||
final SerializationManager serializationManager = new SerializationManager(references,
|
||||
registrationRequired,
|
||||
forbidInterfaceRegistration,
|
||||
disregardingFactory);
|
||||
|
||||
serializationManager.register(PingMessage.class);
|
||||
serializationManager.register(byte[].class);
|
||||
|
||||
serializationManager.register(IESParameters.class, new IesParametersSerializer());
|
||||
serializationManager.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
|
||||
serializationManager.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
|
||||
serializationManager.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
|
||||
serializationManager.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!
|
||||
|
||||
// necessary for the transport of exceptions.
|
||||
serializationManager.register(ArrayList.class, new CollectionSerializer());
|
||||
serializationManager.register(StackTraceElement.class);
|
||||
serializationManager.register(StackTraceElement[].class);
|
||||
|
||||
// extra serializers
|
||||
//noinspection ArraysAsListWithZeroOrOneArgument
|
||||
serializationManager.register(Arrays.asList("")
|
||||
.getClass(), new ArraysAsListSerializer());
|
||||
|
||||
UnmodifiableCollectionsSerializer.registerSerializers(serializationManager);
|
||||
|
||||
return serializationManager;
|
||||
}
|
||||
|
||||
|
||||
private static class ClassSerializer {
|
||||
private static
|
||||
class ClassSerializer {
|
||||
final Class<?> clazz;
|
||||
final Serializer<?> serializer;
|
||||
|
||||
|
@ -133,7 +97,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
this.serializer = serializer;
|
||||
}
|
||||
}
|
||||
private static class ClassSerializer1 {
|
||||
|
||||
|
||||
private static
|
||||
class ClassSerializer1 {
|
||||
final Class<?> clazz;
|
||||
final int id;
|
||||
|
||||
|
@ -142,7 +109,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
this.id = id;
|
||||
}
|
||||
}
|
||||
private static class ClassSerializer2 {
|
||||
|
||||
|
||||
private static
|
||||
class ClassSerializer2 {
|
||||
final Class<?> clazz;
|
||||
final Serializer<?> serializer;
|
||||
final int id;
|
||||
|
@ -153,7 +123,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
this.id = id;
|
||||
}
|
||||
}
|
||||
private static class RemoteIfaceClass {
|
||||
|
||||
|
||||
private static
|
||||
class RemoteIfaceClass {
|
||||
private final Class<?> ifaceClass;
|
||||
|
||||
RemoteIfaceClass(final Class<?> ifaceClass) {
|
||||
|
@ -161,7 +134,9 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
}
|
||||
}
|
||||
|
||||
private static class RemoteImplClass {
|
||||
|
||||
private static
|
||||
class RemoteImplClass {
|
||||
private final Class<?> ifaceClass;
|
||||
private final Class<?> implClass;
|
||||
|
||||
|
@ -171,8 +146,100 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
}
|
||||
}
|
||||
|
||||
public static
|
||||
Serialization DEFAULT() {
|
||||
return DEFAULT(true, true, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
|
||||
* <p>
|
||||
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
|
||||
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
|
||||
*
|
||||
* @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
|
||||
* {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
|
||||
* but typically adds overhead of one byte per object. (should be true)
|
||||
* @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
|
||||
* <p>
|
||||
* If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
|
||||
* Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
|
||||
* appearances of the class within the same object graph are serialized as an int id.
|
||||
* <p>
|
||||
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
|
||||
* drawback of needing to know the classes to be serialized up front.
|
||||
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
|
||||
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
|
||||
* <p>
|
||||
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
|
||||
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
|
||||
* <p>
|
||||
* Generally, one should not register interfaces, because they have no meaning (ignoring "default" implementations in
|
||||
* newer versions of java...)
|
||||
* <p>
|
||||
* @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
|
||||
* an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
|
||||
* Kryo#newDefaultSerializer(Class)
|
||||
*/
|
||||
public static
|
||||
Serialization DEFAULT(final boolean references,
|
||||
final boolean registrationRequired,
|
||||
final boolean implementationRequired,
|
||||
final SerializerFactory factory) {
|
||||
|
||||
final Serialization serialization = new Serialization(references,
|
||||
registrationRequired,
|
||||
implementationRequired,
|
||||
factory);
|
||||
|
||||
serialization.register(PingMessage.class);
|
||||
serialization.register(byte[].class);
|
||||
|
||||
serialization.register(IESParameters.class, new IesParametersSerializer());
|
||||
serialization.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
|
||||
serialization.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
|
||||
serialization.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
|
||||
serialization.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!
|
||||
|
||||
// necessary for the transport of exceptions.
|
||||
serialization.register(ArrayList.class, new CollectionSerializer());
|
||||
serialization.register(StackTraceElement.class);
|
||||
serialization.register(StackTraceElement[].class);
|
||||
|
||||
// extra serializers
|
||||
//noinspection ArraysAsListWithZeroOrOneArgument
|
||||
serialization.register(Arrays.asList("")
|
||||
.getClass(), new ArraysAsListSerializer());
|
||||
|
||||
UnmodifiableCollectionsSerializer.registerSerializers(serialization);
|
||||
|
||||
return serialization;
|
||||
}
|
||||
|
||||
public static
|
||||
Serializer resolveSerializerInstance(Kryo k, Class superClass, Class<? extends Serializer> serializerClass) {
|
||||
try {
|
||||
try {
|
||||
return serializerClass.getConstructor(Kryo.class, Class.class)
|
||||
.newInstance(k, superClass);
|
||||
} catch (NoSuchMethodException ex1) {
|
||||
try {
|
||||
return serializerClass.getConstructor(Kryo.class)
|
||||
.newInstance(k);
|
||||
} catch (NoSuchMethodException ex2) {
|
||||
try {
|
||||
return serializerClass.getConstructor(Class.class)
|
||||
.newInstance(superClass);
|
||||
} catch (NoSuchMethodException ex3) {
|
||||
return serializerClass.newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to create serializer \"" + serializerClass.getName() + "\" for class: " + superClass.getName(), ex);
|
||||
}
|
||||
}
|
||||
private boolean initialized = false;
|
||||
private final ObjectPool<KryoExtra> kryoPool;
|
||||
|
||||
|
@ -185,26 +252,36 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
private final List<Object> classesToRegister = new ArrayList<Object>();
|
||||
|
||||
private boolean usesRmi = false;
|
||||
|
||||
private InvokeMethodSerializer methodSerializer = null;
|
||||
private Serializer<Object> invocationSerializer = null;
|
||||
|
||||
// the purpose of the method cache, is to accelerate looking up methods for specific class
|
||||
private IntMap<CachedMethod[]> methodCache;
|
||||
private RemoteObjectSerializer remoteObjectSerializer;
|
||||
|
||||
// used to track which interface -> implementation, for use by RMI
|
||||
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
|
||||
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
|
||||
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
|
||||
|
||||
|
||||
// reflectASM doesn't work on android
|
||||
private final boolean useAsm = !useUnsafeMemory && !Util.IS_ANDROID;
|
||||
|
||||
/**
|
||||
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
|
||||
* <p>
|
||||
* @param references
|
||||
* If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
|
||||
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
|
||||
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
|
||||
*
|
||||
* @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
|
||||
* {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
|
||||
* but typically adds overhead of one byte per object. (should be true)
|
||||
* <p>
|
||||
* @param registrationRequired
|
||||
* If true, an exception is thrown when an unregistered class is encountered.
|
||||
* @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
|
||||
* <p>
|
||||
* If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
|
||||
* Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
|
||||
|
@ -213,8 +290,7 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
|
||||
* drawback of needing to know the classes to be serialized up front.
|
||||
* <p>
|
||||
* @param forbidInterfaceRegistration
|
||||
* If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
|
||||
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
|
||||
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
|
||||
* <p>
|
||||
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
|
||||
|
@ -222,26 +298,27 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
* <p>
|
||||
* Generally, one should not register interfaces, because they have no meaning (ignoring "default" implementations in
|
||||
* newer versions of java...)
|
||||
* @param factory
|
||||
* Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
|
||||
* <p>
|
||||
* @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
|
||||
* an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
|
||||
* Kryo#newDefaultSerializer(Class)
|
||||
* <p>
|
||||
*/
|
||||
public
|
||||
SerializationManager(final boolean references, final boolean registrationRequired, final boolean forbidInterfaceRegistration, final SerializerFactory factory) {
|
||||
this.forbidInterfaceRegistration = forbidInterfaceRegistration;
|
||||
Serialization(final boolean references,
|
||||
final boolean registrationRequired,
|
||||
final boolean implementationRequired,
|
||||
final SerializerFactory factory) {
|
||||
this.forbidInterfaceRegistration = implementationRequired;
|
||||
this.kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
|
||||
@Override
|
||||
public
|
||||
KryoExtra create() {
|
||||
synchronized (SerializationManager.this) {
|
||||
KryoExtra kryo = new KryoExtra(SerializationManager.this);
|
||||
|
||||
synchronized (Serialization.this) {
|
||||
// we HAVE to pre-allocate the KRYOs
|
||||
boolean useAsm = !useUnsafeMemory;
|
||||
KryoExtra kryo = new KryoExtra(Serialization.this);
|
||||
|
||||
kryo.getFieldSerializerConfig().setUseAsm(useAsm);
|
||||
kryo.getFieldSerializerConfig()
|
||||
.setUseAsm(useAsm);
|
||||
kryo.setRegistrationRequired(registrationRequired);
|
||||
|
||||
kryo.setReferences(references);
|
||||
|
@ -260,11 +337,11 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
kryo.register(InvocationHandler.class, invocationSerializer);
|
||||
}
|
||||
|
||||
|
||||
// All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
|
||||
for (Object clazz : classesToRegister) {
|
||||
if (clazz instanceof Class) {
|
||||
kryo.register((Class)clazz);
|
||||
Class aClass = (Class) clazz;
|
||||
kryo.register(aClass);
|
||||
}
|
||||
else if (clazz instanceof ClassSerializer) {
|
||||
ClassSerializer classSerializer = (ClassSerializer) clazz;
|
||||
|
@ -279,31 +356,32 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id);
|
||||
}
|
||||
else if (clazz instanceof RemoteIfaceClass) {
|
||||
// THIS IS DONE ON THE CLIENT
|
||||
// THIS IS DONE ON THE "CLIENT"
|
||||
// "server" means the side of the connection that has the implementation details for the RMI object
|
||||
// "client" means the side of the connection that accesses the "server" side object via a proxy object
|
||||
// the client will NEVER send this object to the server.
|
||||
// the server will ONLY send this object to the client
|
||||
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
|
||||
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
|
||||
|
||||
// registers the interface, so that when it is READ, it becomes a "magic" proxy object
|
||||
kryo.register(remoteIfaceClass.ifaceClass, remoteObjectSerializer);
|
||||
}
|
||||
else if (clazz instanceof RemoteImplClass) {
|
||||
// THIS IS DONE ON THE SERVER
|
||||
// THIS IS DONE ON THE "SERVER"
|
||||
// "server" means the side of the connection that has the implementation details for the RMI object
|
||||
// "client" means the side of the connection that accesses the "server" side object via a proxy object
|
||||
// the client will NEVER send this object to the server.
|
||||
// the server will ONLY send this object to the client
|
||||
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
|
||||
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
|
||||
|
||||
// registers the implementation, so that when it is WRITTEN, it becomes a "magic" proxy object
|
||||
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
|
||||
// if this is over-written, we don't care.
|
||||
rmiIdToImpl.put(id, remoteImplClass.implClass);
|
||||
rmiIdToIface.put(id, remoteImplClass.ifaceClass);
|
||||
rmiIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
|
||||
rmiIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,6 +395,32 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
});
|
||||
}
|
||||
|
||||
private static
|
||||
ArrayList<Class<?>> getHierarchy(Class<?> clazz) {
|
||||
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
|
||||
LinkedList<Class<?>> parseClasses = new LinkedList<Class<?>>();
|
||||
parseClasses.add(clazz);
|
||||
|
||||
Class<?> nextClass;
|
||||
while (!parseClasses.isEmpty()) {
|
||||
nextClass = parseClasses.removeFirst();
|
||||
allClasses.add(nextClass);
|
||||
|
||||
// add all interfaces from our class (if any)
|
||||
parseClasses.addAll(Arrays.asList(nextClass.getInterfaces()));
|
||||
|
||||
Class<?> superclass = nextClass.getSuperclass();
|
||||
if (superclass != null) {
|
||||
parseClasses.add(superclass);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the first class, because we don't need it
|
||||
allClasses.remove(clazz);
|
||||
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the class using the lowest, next available integer ID and the {@link Kryo#getDefaultSerializer(Class) default serializer}.
|
||||
* If the class is already registered, the existing entry is updated with the new serializer.
|
||||
|
@ -334,7 +438,8 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
}
|
||||
else if (forbidInterfaceRegistration && clazz.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an interface for serialization. It must be an implementation.");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
classesToRegister.add(clazz);
|
||||
}
|
||||
|
||||
|
@ -420,6 +525,60 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
|
||||
*
|
||||
* @return true if there are classes that have been registered for RMI
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
boolean initRmiSerialization() {
|
||||
if (!usesRmi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
|
||||
return true;
|
||||
}
|
||||
|
||||
methodSerializer = new InvokeMethodSerializer();
|
||||
invocationSerializer = new InvocationHandlerSerializer(logger);
|
||||
remoteObjectSerializer = new RemoteObjectSerializer();
|
||||
methodCache = new IntMap<CachedMethod[]>();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return takes a kryo instance from the pool.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
KryoExtra takeKryo() {
|
||||
return kryoPool.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a kryo instance to the pool.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void returnKryo(KryoExtra kryo) {
|
||||
kryoPool.put(kryo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(Class<?> implementation) {
|
||||
return rmiImplToIface.get(implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* <p>
|
||||
|
@ -442,6 +601,7 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
|
||||
usesRmi = true;
|
||||
classesToRegister.add(new RemoteIfaceClass(ifaceClass));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -491,113 +651,40 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
}
|
||||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
|
||||
*
|
||||
* @return true if there are classes that have been registered for RMI
|
||||
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
|
||||
* <p>
|
||||
* No crypto and no sequence number
|
||||
* <p>
|
||||
* There is a small speed penalty if there were no kryo's available to use.
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
boolean initRmiSerialization() {
|
||||
if (!usesRmi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
|
||||
return true;
|
||||
}
|
||||
|
||||
methodSerializer = new InvokeMethodSerializer();
|
||||
invocationSerializer = new InvocationHandlerSerializer(logger);
|
||||
remoteObjectSerializer = new RemoteObjectSerializer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
|
||||
* is already in use by a different type, a {@link KryoException} is thrown.
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
void finishInit() {
|
||||
initialized = true;
|
||||
|
||||
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
|
||||
// correctly and (if not) we are are notified on the initial thread (instead of on the network udpate thread)
|
||||
kryoPool.put(takeKryo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean 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 ID (which is the ID for the registered interface)
|
||||
*
|
||||
* @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<?> getRmiImpl(int objectId) {
|
||||
return rmiIdToImpl.get(objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified interface
|
||||
*
|
||||
* @return the corresponding implementation
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiImpl(Class<?> iface) {
|
||||
return rmiIfaceToImpl.get(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(Class<?> implementation) {
|
||||
return rmiImplToIface.get(implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return takes a kryo instance from the pool.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
KryoExtra takeKryo() {
|
||||
return kryoPool.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a kryo instance to the pool.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void returnKryo(KryoExtra kryo) {
|
||||
public final
|
||||
void write(final ByteBuf buffer, final Object message) throws IOException {
|
||||
final KryoExtra kryo = kryoPool.take();
|
||||
try {
|
||||
kryo.write(buffer, message);
|
||||
} finally {
|
||||
kryoPool.put(kryo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an object from the buffer.
|
||||
* <p>
|
||||
* No crypto and no sequence number
|
||||
*
|
||||
* @param length should ALWAYS be the length of the expected object!
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
Object read(final ByteBuf buffer, final int length) throws IOException {
|
||||
final KryoExtra kryo = kryoPool.take();
|
||||
try {
|
||||
return kryo.read(buffer);
|
||||
} finally {
|
||||
kryoPool.put(kryo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the class and object using an available kryo instance
|
||||
|
@ -652,40 +739,76 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
|
|||
}
|
||||
|
||||
/**
|
||||
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
|
||||
* <p>
|
||||
* No crypto and no sequence number
|
||||
* <p>
|
||||
* There is a small speed penalty if there were no kryo's available to use.
|
||||
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
|
||||
* is already in use by a different type, a {@link KryoException} is thrown.
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
void write(final ByteBuf buffer, final Object message) throws IOException {
|
||||
final KryoExtra kryo = kryoPool.take();
|
||||
public synchronized
|
||||
void finishInit() {
|
||||
initialized = true;
|
||||
|
||||
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
|
||||
// correctly and (if not) we are are notified on the initial thread (instead of on the network update thread)
|
||||
KryoExtra kryo = null;
|
||||
try {
|
||||
kryo.write(buffer, message);
|
||||
} finally {
|
||||
kryoPool.put(kryo);
|
||||
kryo = takeKryo();
|
||||
|
||||
ClassResolver classResolver = kryo.getClassResolver();
|
||||
|
||||
// now initialize the RMI cached methods, so that they are "final" when the network threads need access to it.
|
||||
for (Object clazz : classesToRegister) {
|
||||
if (clazz instanceof RemoteIfaceClass) {
|
||||
// THIS IS DONE ON THE "CLIENT"
|
||||
// "server" means the side of the connection that has the implementation details for the RMI object
|
||||
// "client" means the side of the connection that accesses the "server" side object via a proxy object
|
||||
// the client will NEVER send this object to the server.
|
||||
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
|
||||
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
|
||||
int id = classResolver.getRegistration(remoteIfaceClass.ifaceClass)
|
||||
.getId();
|
||||
|
||||
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(logger, kryo, useAsm,
|
||||
remoteIfaceClass.ifaceClass,
|
||||
null,
|
||||
id);
|
||||
methodCache.put(id, cachedMethods);
|
||||
}
|
||||
else if (clazz instanceof RemoteImplClass) {
|
||||
// THIS IS DONE ON THE "SERVER"
|
||||
// "server" means the side of the connection that has the implementation details for the RMI object
|
||||
// "client" means the side of the connection that accesses the "server" side object via a proxy object
|
||||
// the client will NEVER send this object to the server.
|
||||
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
|
||||
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
|
||||
int id = classResolver.getRegistration(remoteImplClass.implClass)
|
||||
.getId();
|
||||
|
||||
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(logger, kryo, useAsm,
|
||||
remoteImplClass.ifaceClass,
|
||||
remoteImplClass.implClass,
|
||||
id);
|
||||
methodCache.put(id, cachedMethods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an object from the buffer.
|
||||
* <p>
|
||||
* No crypto and no sequence number
|
||||
*
|
||||
* @param length should ALWAYS be the length of the expected object!
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
Object read(final ByteBuf buffer, final int length) throws IOException {
|
||||
final KryoExtra kryo = kryoPool.take();
|
||||
try {
|
||||
return kryo.read(buffer);
|
||||
} finally {
|
||||
if (kryo != null) {
|
||||
kryoPool.put(kryo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
CachedMethod[] getMethods(final int classId) {
|
||||
return methodCache.get(classId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean initialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
|
|
@ -31,7 +31,7 @@ import dorkbox.network.PingPongTest.TYPE;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -54,7 +54,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
|
@ -65,7 +65,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.UDP);
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.junit.Test;
|
|||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class ClientSendTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.junit.Test;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPointBase;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -42,7 +42,7 @@ class ConnectionTest extends BaseTest {
|
|||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.localChannelName = EndPointBase.LOCAL_CHANNEL;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
|
@ -58,7 +58,7 @@ class ConnectionTest extends BaseTest {
|
|||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
|
@ -77,7 +77,7 @@ class ConnectionTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
|
|
|
@ -36,7 +36,7 @@ import dorkbox.network.connection.idle.IdleListener;
|
|||
import dorkbox.network.connection.idle.IdleListenerTCP;
|
||||
import dorkbox.network.connection.idle.IdleListenerUDP;
|
||||
import dorkbox.network.connection.idle.InputStreamSender;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -60,7 +60,7 @@ class IdleTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
|
||||
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
|
||||
|
||||
streamSpecificType(largeDataSize, configuration, ConnectionType.TCP);
|
||||
|
||||
|
@ -70,7 +70,7 @@ class IdleTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
|
||||
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
|
||||
|
||||
streamSpecificType(largeDataSize, configuration, ConnectionType.UDP);
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ class IdleTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
|
@ -100,7 +100,7 @@ class IdleTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.junit.Test;
|
|||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -50,7 +50,7 @@ class LargeResizeBufferTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.junit.Test;
|
|||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class MultipleServerTest extends BaseTest {
|
|||
configuration1.tcpPort = tcpPort;
|
||||
configuration1.udpPort = udpPort;
|
||||
configuration1.localChannelName = "chan1";
|
||||
configuration1.serialization = SerializationManager.DEFAULT();
|
||||
configuration1.serialization = Serialization.DEFAULT();
|
||||
configuration1.serialization.register(String[].class);
|
||||
|
||||
Server server1 = new Server(configuration1);
|
||||
|
@ -68,7 +68,7 @@ class MultipleServerTest extends BaseTest {
|
|||
configuration2.tcpPort = tcpPort + 1;
|
||||
configuration2.udpPort = udpPort + 1;
|
||||
configuration2.localChannelName = "chan2";
|
||||
configuration2.serialization = SerializationManager.DEFAULT();
|
||||
configuration2.serialization = Serialization.DEFAULT();
|
||||
configuration2.serialization.register(String[].class);
|
||||
|
||||
Server server2 = new Server(configuration2);
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.junit.Test;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.Listeners;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -61,7 +61,7 @@ class MultipleThreadTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.register(String[].class);
|
||||
configuration.serialization.register(DataClass.class);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.junit.Test;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.Listeners;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -47,7 +47,7 @@ class PingPongLocalTest extends BaseTest {
|
|||
populateData(dataLOCAL);
|
||||
|
||||
Configuration configuration = Configuration.localOnly();
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import dorkbox.network.connection.Connection;
|
|||
import dorkbox.network.connection.EndPointBase;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.Listeners;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -60,7 +60,7 @@ class PingPongTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import dorkbox.network.connection.Connection;
|
|||
import dorkbox.network.connection.EndPointBase;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.Listeners;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -54,7 +54,7 @@ class UnregisteredClassTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
|
||||
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
|
||||
|
||||
|
||||
System.err.println("Running test " + this.tries + " times, please wait for it to finish.");
|
||||
|
|
|
@ -50,7 +50,7 @@ import dorkbox.network.Server;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -64,10 +64,10 @@ class RmiGlobalTest extends BaseTest {
|
|||
private final TestCow globalRemoteClientObject = new TestCowImpl();
|
||||
|
||||
private static
|
||||
void runTest(final Connection connection, final TestCow rObject, final TestCow test, final int remoteObjectID) {
|
||||
void runTest(final Connection connection, final TestCow globalObject, final TestCow test, final int remoteObjectID) {
|
||||
System.err.println("Starting test for: " + remoteObjectID);
|
||||
|
||||
assertEquals(rObject.hashCode(), test.hashCode());
|
||||
assertEquals(globalObject.hashCode(), test.hashCode());
|
||||
RemoteObject remoteObject = (RemoteObject) test;
|
||||
|
||||
// Default behavior. RMI is transparent, method calls behave like normal
|
||||
|
@ -110,7 +110,7 @@ class RmiGlobalTest extends BaseTest {
|
|||
try {
|
||||
test.throwException();
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
System.err.println("\tExpected.");
|
||||
System.err.println("\tExpected exception! " + ex.getMessage());
|
||||
caught = true;
|
||||
}
|
||||
assertTrue(caught);
|
||||
|
@ -195,16 +195,15 @@ class RmiGlobalTest extends BaseTest {
|
|||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
|
||||
|
@ -267,7 +266,7 @@ class RmiGlobalTest extends BaseTest {
|
|||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
|
|
|
@ -29,10 +29,9 @@ import dorkbox.network.Configuration;
|
|||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
import dorkbox.util.serialization.IgnoreSerialization;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
|
@ -48,21 +47,18 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
*
|
||||
* Specifically, from CachedMethod.java
|
||||
*
|
||||
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
// the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
// the list of parameters.
|
||||
//
|
||||
// for example:
|
||||
// Interface: foo(String x)
|
||||
// Impl: foo(Connection c, String x)
|
||||
//
|
||||
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
|
||||
// This MUST hold valid for both remote and local connection types.
|
||||
|
||||
// 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.
|
||||
* In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
|
||||
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
|
||||
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
|
||||
* the interface, and the implType may override the method, so that we add the connection as the first in
|
||||
* the list of parameters.
|
||||
*
|
||||
* for example:
|
||||
* Interface: foo(String x)
|
||||
* Impl: foo(Connection c, String x)
|
||||
*
|
||||
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
|
||||
* instead of the method that would NORMALLY be called.
|
||||
*/
|
||||
@Test
|
||||
public
|
||||
|
@ -71,7 +67,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
@ -82,7 +78,6 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
addEndPoint(server);
|
||||
server.bind(false);
|
||||
|
||||
|
||||
server.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, OtherObjectImpl>() {
|
||||
@Override
|
||||
|
@ -105,7 +100,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
|
||||
|
@ -190,8 +185,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
|
||||
private static
|
||||
class TestObjectImpl implements TestObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
private final transient int ID = idCounter.getAndIncrement();
|
||||
|
||||
@Rmi
|
||||
private final OtherObject otherObject = new OtherObjectImpl();
|
||||
|
@ -241,8 +235,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
|
||||
private static
|
||||
class OtherObjectImpl implements OtherObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
private final transient int ID = idCounter.getAndIncrement();
|
||||
|
||||
private float aFloat;
|
||||
|
||||
|
|
|
@ -48,10 +48,9 @@ import dorkbox.network.Configuration;
|
|||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
import dorkbox.util.serialization.IgnoreSerialization;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
|
@ -68,7 +67,7 @@ class RmiSendObjectTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
@ -103,7 +102,7 @@ class RmiSendObjectTest extends BaseTest {
|
|||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
|
||||
|
@ -180,8 +179,7 @@ class RmiSendObjectTest extends BaseTest {
|
|||
|
||||
private static
|
||||
class TestObjectImpl implements TestObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
private final transient int ID = idCounter.getAndIncrement();
|
||||
|
||||
@Rmi
|
||||
private final OtherObject otherObject = new OtherObjectImpl();
|
||||
|
@ -216,8 +214,7 @@ class RmiSendObjectTest extends BaseTest {
|
|||
|
||||
private static
|
||||
class OtherObjectImpl implements OtherObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
private final transient int ID = idCounter.getAndIncrement();
|
||||
|
||||
private float aFloat;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ import dorkbox.network.Server;
|
|||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.Listeners;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -167,6 +167,7 @@ class RmiTest extends BaseTest {
|
|||
.TCP(m)
|
||||
.flush();
|
||||
|
||||
System.out.println("Finished tests");
|
||||
}
|
||||
|
||||
public static
|
||||
|
@ -184,7 +185,7 @@ class RmiTest extends BaseTest {
|
|||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
|
@ -248,7 +249,7 @@ class RmiTest extends BaseTest {
|
|||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
|
|
|
@ -4,9 +4,7 @@ package dorkbox.network.rmi;
|
|||
*
|
||||
*/
|
||||
public
|
||||
interface TestCow {
|
||||
void throwException();
|
||||
|
||||
interface TestCow extends TestCowBase {
|
||||
void moo();
|
||||
|
||||
void moo(String value);
|
||||
|
|
9
test/dorkbox/network/rmi/TestCowBase.java
Normal file
9
test/dorkbox/network/rmi/TestCowBase.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
/**
|
||||
* This is a different interface so we can also test CachedMethod operations
|
||||
*/
|
||||
public
|
||||
interface TestCowBase {
|
||||
void throwException();
|
||||
}
|
27
test/dorkbox/network/rmi/TestCowBaseImpl.java
Normal file
27
test/dorkbox/network/rmi/TestCowBaseImpl.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class TestCowBaseImpl implements TestCowBase {
|
||||
// has to start at 1, because UDP method invocations ignore return values
|
||||
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
|
||||
|
||||
public
|
||||
TestCowBaseImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void throwException() {
|
||||
throw new UnsupportedOperationException("Why would I do that?");
|
||||
}
|
||||
|
||||
public
|
||||
int id() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
|
@ -1,48 +1,36 @@
|
|||
package dorkbox.network.rmi;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class TestCowImpl implements TestCow {
|
||||
// has to start at 1, because UDP method invocations ignore return values
|
||||
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
|
||||
|
||||
public long value = System.currentTimeMillis();
|
||||
public int moos;
|
||||
class TestCowImpl extends TestCowBaseImpl implements TestCow {
|
||||
private int moos;
|
||||
private final int id = ID_COUNTER.getAndIncrement();
|
||||
|
||||
public
|
||||
TestCowImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void throwException() {
|
||||
throw new UnsupportedOperationException("Why would I do that?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo() {
|
||||
this.moos++;
|
||||
System.out.println("Moo!");
|
||||
System.out.println("Moo! " + this.moos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value) {
|
||||
this.moos += 2;
|
||||
System.out.println("Moo: " + value);
|
||||
System.out.println("Moo! " + this.moos + " :" + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value, long delay) {
|
||||
this.moos += 4;
|
||||
System.out.println("Moo: " + value + " (" + delay + ")");
|
||||
System.out.println("Moo! " + this.moos + " :" + value + " (" + delay + ")");
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import dorkbox.network.connection.Connection;
|
|||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
|
||||
/**
|
||||
|
@ -82,13 +82,13 @@ class TestClient
|
|||
configuration.udpPort = 2001;
|
||||
configuration.host = "localhost";
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
|
||||
try {
|
||||
Client client = new Client(configuration);
|
||||
final Client client = new Client(configuration);
|
||||
// client.setIdleTimeout(0);
|
||||
|
||||
client.listeners()
|
||||
|
@ -110,7 +110,15 @@ class TestClient
|
|||
public
|
||||
void run() {
|
||||
RmiTest.runTests(connection, remoteObject, 1);
|
||||
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.err.println("DONE");
|
||||
client.stop();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
@ -124,7 +132,7 @@ class TestClient
|
|||
|
||||
client.connect(3330);
|
||||
|
||||
Thread.sleep(999999999);
|
||||
client.waitForShutdown();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import dorkbox.network.Server;
|
|||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import dorkbox.network.rmi.TestCowImpl;
|
||||
import dorkbox.network.serialization.SerializationManager;
|
||||
import dorkbox.network.serialization.Serialization;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
|
@ -24,7 +24,7 @@ class TestServer
|
|||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
|
||||
configuration.serialization = SerializationManager.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user