RMI is now more robust and forgiving
This commit is contained in:
parent
c927f87316
commit
33853d44e3
|
@ -53,7 +53,7 @@ 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 boolean overriddenMethod;
|
||||
public Method overriddenMethod;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Serializer[] serializers;
|
||||
|
@ -62,4 +62,10 @@ class CachedMethod {
|
|||
Object invoke(final Connection connection, Object target, Object[] args) throws Exception {
|
||||
return this.method.invoke(target, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return "CachedMethod{" + method.getName() + ", methodClassID=" + methodClassID + ", methodIndex=" + methodIndex + '}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ public
|
|||
class InvokeMethod implements RmiMessage {
|
||||
public int objectID; // the registered kryo ID for the object
|
||||
|
||||
// This class is NOT sent across the wire. We use a custom serializer to manage this.
|
||||
public CachedMethod cachedMethod;
|
||||
public Object[] args;
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
|
@ -47,6 +49,7 @@ import dorkbox.network.connection.KryoExtra;
|
|||
*/
|
||||
public
|
||||
class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public
|
||||
InvokeMethodSerializer() {
|
||||
|
@ -56,9 +59,12 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
@SuppressWarnings("rawtypes")
|
||||
public
|
||||
void write(final Kryo kryo, final Output output, final InvokeMethod object) {
|
||||
// System.err.println(":: objectID " + object.objectID);
|
||||
// System.err.println(":: methodClassID " + object.cachedMethod.methodClassID);
|
||||
// System.err.println(":: methodIndex " + object.cachedMethod.methodIndex);
|
||||
if (DEBUG) {
|
||||
System.err.println("WRITING");
|
||||
System.err.println(":: objectID " + object.objectID);
|
||||
System.err.println(":: methodClassID " + object.cachedMethod.methodClassID);
|
||||
System.err.println(":: methodIndex " + object.cachedMethod.methodIndex);
|
||||
}
|
||||
|
||||
output.writeInt(object.objectID, true);
|
||||
output.writeInt(object.cachedMethod.methodClassID, true);
|
||||
|
@ -90,9 +96,12 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
int methodClassID = input.readInt(true);
|
||||
byte methodIndex = input.readByte();
|
||||
|
||||
// System.err.println(":: objectID " + objectID);
|
||||
// System.err.println(":: methodClassID " + methodClassID);
|
||||
// System.err.println(":: methodIndex " + methodIndex);
|
||||
if (DEBUG) {
|
||||
System.err.println("READING");
|
||||
System.err.println(":: objectID " + objectID);
|
||||
System.err.println(":: methodClassID " + methodClassID);
|
||||
System.err.println(":: methodIndex " + methodIndex);
|
||||
}
|
||||
|
||||
CachedMethod cachedMethod;
|
||||
try {
|
||||
|
@ -107,10 +116,13 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
Object[] args;
|
||||
Serializer<?>[] serializers = cachedMethod.serializers;
|
||||
|
||||
Method method;
|
||||
int argStartIndex;
|
||||
|
||||
if (cachedMethod.overriddenMethod) {
|
||||
if (cachedMethod.overriddenMethod != null) {
|
||||
// did we override our cached method? This is not common.
|
||||
method = cachedMethod.overriddenMethod;
|
||||
|
||||
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
|
||||
argStartIndex = 1;
|
||||
|
||||
|
@ -118,12 +130,15 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
|||
args[0] = ((KryoExtra) kryo).connection;
|
||||
}
|
||||
else {
|
||||
method = cachedMethod.method;
|
||||
argStartIndex = 0;
|
||||
args = new Object[serializers.length];
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
|
||||
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
// we don't start at 0 for the arguments, in case we have an overwritten method (in which case, the 1st arg is always "Connection.class")
|
||||
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import com.esotericsoftware.kryo.Kryo;
|
|||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
|
@ -50,8 +51,12 @@ import dorkbox.network.connection.KryoExtra;
|
|||
public
|
||||
class RemoteObjectSerializer<T> extends Serializer<T> {
|
||||
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface;
|
||||
|
||||
public
|
||||
RemoteObjectSerializer() {
|
||||
RemoteObjectSerializer(final IdentityMap<Class<?>, Class<?>> rmiImplToIface) {
|
||||
super(false);
|
||||
this.rmiImplToIface = rmiImplToIface;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,9 +70,13 @@ class RemoteObjectSerializer<T> extends Serializer<T> {
|
|||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public
|
||||
T read(Kryo kryo, Input input, Class type) {
|
||||
T read(Kryo kryo, Input input, Class implementationType) {
|
||||
KryoExtra kryoExtra = (KryoExtra) kryo;
|
||||
int objectID = input.readInt(true);
|
||||
return (T) kryoExtra.connection.getProxyObject(objectID, type);
|
||||
|
||||
// We have to lookup the iface, since the proxy object requires it
|
||||
Class<?> iface = rmiImplToIface.get(implementationType);
|
||||
|
||||
return (T) kryoExtra.connection.getProxyObject(objectID, iface);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ import dorkbox.util.collections.LockFreeIntBiMap;
|
|||
* object transformation (because there is no serialization occurring) using a series of weak hashmaps.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link
|
||||
* Objects are {@link RmiSerializationManager#registerRmi(Class, Class)}, and endpoint connections can then {@link
|
||||
* Connection#createRemoteObject(Class, RemoteObjectCallback)} for the registered objects.
|
||||
* <p/>
|
||||
* It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a return value which
|
||||
|
@ -236,7 +236,7 @@ class RmiBridge {
|
|||
.append(argString)
|
||||
.append(")");
|
||||
|
||||
if (cachedMethod.overriddenMethod) {
|
||||
if (cachedMethod.overriddenMethod != null) {
|
||||
// did we override our cached method? This is not common.
|
||||
stringBuilder.append(" [Connection method override]");
|
||||
}
|
||||
|
@ -279,8 +279,8 @@ class RmiBridge {
|
|||
}
|
||||
|
||||
InvokeMethodResult invokeMethodResult = new InvokeMethodResult();
|
||||
invokeMethodResult.objectID = invokeMethod.objectID;
|
||||
invokeMethodResult.responseID = (byte) responseID;
|
||||
invokeMethodResult.rmiObjectId = invokeMethod.objectID;
|
||||
invokeMethodResult.responseId = (byte) responseID;
|
||||
|
||||
|
||||
// Do not return non-primitives if transmitReturnVal is false
|
||||
|
|
|
@ -101,7 +101,7 @@ class RmiObjectLocalHandler extends RmiObjectHandler {
|
|||
|
||||
int argStartIndex;
|
||||
|
||||
if (cachedMethod.overriddenMethod) {
|
||||
if (cachedMethod.overriddenMethod != null) {
|
||||
// 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;
|
||||
|
|
|
@ -54,8 +54,12 @@ class RmiObjectNetworkHandler extends RmiObjectHandler {
|
|||
//
|
||||
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
|
||||
|
||||
// have to lookup the implementation class
|
||||
Class<?> rmiImpl = connection.getEndPoint().getSerialization().getRmiImpl(interfaceClass);
|
||||
|
||||
|
||||
// For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically.
|
||||
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId);
|
||||
RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, rmiImpl, callbackId);
|
||||
connection.send(registrationResult);
|
||||
// connection transport is flushed in calling method (don't need to do it here)
|
||||
}
|
||||
|
|
|
@ -42,12 +42,6 @@ class RmiRegistration implements RmiMessage {
|
|||
*/
|
||||
public int callbackId;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private
|
||||
RmiRegistration() {
|
||||
// for serialization
|
||||
}
|
||||
|
||||
/**
|
||||
* When requesting a new or existing remote object
|
||||
* SENT FROM "local" -> "remote"
|
||||
|
|
83
src/dorkbox/network/rmi/RmiRegistrationSerializer.java
Normal file
83
src/dorkbox/network/rmi/RmiRegistrationSerializer.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
/**
|
||||
* This is required, because with RMI, it is possible that the IMPL and IFACE can have DIFFERENT class IDs, in which case, the "client" cannot read the correct
|
||||
* objects (because the IMPL class might not be registered, or that ID might be registered to a different class)
|
||||
*/
|
||||
public
|
||||
class RmiRegistrationSerializer extends Serializer<RmiRegistration> {
|
||||
|
||||
public
|
||||
RmiRegistrationSerializer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void write(Kryo kryo, Output output, RmiRegistration object) {
|
||||
output.writeBoolean(object.isRequest);
|
||||
output.writeInt(object.rmiId, true);
|
||||
output.writeInt(object.callbackId, true);
|
||||
|
||||
int id = kryo.getRegistration(object.interfaceClass).getId();
|
||||
output.writeInt(id, true);
|
||||
|
||||
if (object.remoteObject != null) {
|
||||
KryoExtra kryoExtra = (KryoExtra) kryo;
|
||||
id = kryoExtra.connection.getRegisteredId(object.remoteObject);
|
||||
} else {
|
||||
// can be < 0 or >= RmiBridge.INVALID_RMI (Integer.MAX_VALUE)
|
||||
id = -1;
|
||||
}
|
||||
output.writeInt(id, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public
|
||||
RmiRegistration read(Kryo kryo, Input input, Class implementationType) {
|
||||
|
||||
boolean isRequest = input.readBoolean();
|
||||
int rmiId = input.readInt(true);
|
||||
int callbackId = input.readInt(true);
|
||||
|
||||
int interfaceClassId = input.readInt(true);
|
||||
int remoteObjectId = input.readInt(false);
|
||||
|
||||
|
||||
// // We have to lookup the iface, since the proxy object requires it
|
||||
Class iface = kryo.getRegistration(interfaceClassId).getType();
|
||||
|
||||
Object remoteObject = null;
|
||||
if (remoteObjectId >= 0 && remoteObjectId < RmiBridge.INVALID_RMI) {
|
||||
KryoExtra kryoExtra = (KryoExtra) kryo;
|
||||
remoteObject = kryoExtra.connection.getProxyObject(remoteObjectId, iface);
|
||||
}
|
||||
|
||||
RmiRegistration rmiRegistration = new RmiRegistration(iface, rmiId, callbackId, remoteObject);
|
||||
rmiRegistration.isRequest = isRequest;
|
||||
|
||||
return rmiRegistration;
|
||||
}
|
||||
}
|
|
@ -100,18 +100,13 @@ class RmiUtils {
|
|||
|
||||
|
||||
/**
|
||||
* @param logger
|
||||
* @param kryo
|
||||
* @param asmEnabled
|
||||
* @param iFace this is never null.
|
||||
* @param impl this is NULL on the rmi "client" side. This is NOT NULL on the "server" side (where the object lives)
|
||||
* @param classId
|
||||
* @return
|
||||
*/
|
||||
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;
|
||||
MethodAccess ifaceAsmMethodAccess = null;
|
||||
MethodAccess implAsmMethodAccess = 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.
|
||||
|
@ -120,22 +115,24 @@ class RmiUtils {
|
|||
final int size = methods.length;
|
||||
final CachedMethod[] cachedMethods = new CachedMethod[size];
|
||||
|
||||
final Method[] implMethods;
|
||||
|
||||
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);
|
||||
|
||||
implMethods = getMethods(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) {
|
||||
implMethodAccess = getReflectAsmMethod(logger, impl);
|
||||
implAsmMethodAccess = getReflectAsmMethod(logger, impl);
|
||||
}
|
||||
} else {
|
||||
implMethods = null;
|
||||
}
|
||||
|
||||
// reflectASM
|
||||
|
@ -143,7 +140,7 @@ class RmiUtils {
|
|||
// 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);
|
||||
ifaceAsmMethodAccess = getReflectAsmMethod(logger, iFace);
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
@ -153,50 +150,44 @@ class RmiUtils {
|
|||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
// copy because they can be overridden
|
||||
boolean overriddenMethod = false;
|
||||
MethodAccess tweakMethodAccess = ifaceMethodAccess;
|
||||
CachedMethod cachedMethod = null;
|
||||
MethodAccess ifaceORimplMethodAccess = ifaceAsmMethodAccess;
|
||||
|
||||
// reflectAsm doesn't like "Object" class methods
|
||||
boolean canUseAsm = asmEnabled && method.getDeclaringClass() != Object.class;
|
||||
|
||||
Method overwrittenMethod = null;
|
||||
|
||||
// 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 (implMethods != null) {
|
||||
overwrittenMethod = getOverwriteMethodWithConnectionParam(implMethods, method);
|
||||
if (overwrittenMethod != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Overridden method: {}.{}", impl, method.getName());
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Overridden method: {}.{}", impl, method.getName());
|
||||
// still might be null!
|
||||
ifaceORimplMethodAccess = implAsmMethodAccess;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CachedMethod cachedMethod = null;
|
||||
// reflectAsm doesn't like "Object" class methods...
|
||||
if (tweakMethodAccess != null && method.getDeclaringClass() != Object.class) {
|
||||
if (canUseAsm) {
|
||||
try {
|
||||
final int index = tweakMethodAccess.getIndex(method.getName(), parameterTypes);
|
||||
int index;
|
||||
if (overwrittenMethod != null) {
|
||||
// have to take into account the overwritten method's first parameter will ALWAYS be "Connection"
|
||||
index = ifaceORimplMethodAccess.getIndex(method.getName(), overwrittenMethod.getParameterTypes());
|
||||
} else {
|
||||
index = ifaceORimplMethodAccess.getIndex(method.getName(), parameterTypes);
|
||||
}
|
||||
|
||||
|
||||
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
|
||||
asmCachedMethod.methodAccessIndex = index;
|
||||
asmCachedMethod.methodAccess = tweakMethodAccess;
|
||||
asmCachedMethod.methodAccess = ifaceORimplMethodAccess;
|
||||
asmCachedMethod.name = method.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 {}.{} (using java reflection instead)", declaringClass, method.getName(), e);
|
||||
|
@ -207,10 +198,11 @@ class RmiUtils {
|
|||
cachedMethod = new CachedMethod();
|
||||
}
|
||||
|
||||
cachedMethod.overriddenMethod = overriddenMethod;
|
||||
|
||||
cachedMethod.methodClassID = classId;
|
||||
|
||||
// we ALSO have to setup "normal" reflection access to these methods
|
||||
// this MIGHT be null, but if it is not, this is the method we will invoke INSTEAD of the "normal" method
|
||||
cachedMethod.overriddenMethod = overwrittenMethod;
|
||||
cachedMethod.method = method;
|
||||
cachedMethod.methodIndex = i;
|
||||
|
||||
|
@ -229,9 +221,9 @@ class RmiUtils {
|
|||
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)
|
||||
* NOTE: does not null check
|
||||
*
|
||||
* @param implMethods methods from the implementation
|
||||
* @param origMethods methods from the interface
|
||||
|
@ -241,47 +233,62 @@ class RmiUtils {
|
|||
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;
|
||||
Method overwriteMethodsWithConnectionParam = getOverwriteMethodWithConnectionParam(implMethods, origMethod);
|
||||
if (overwriteMethodsWithConnectionParam != null) {
|
||||
origMethods[i] = overwriteMethodsWithConnectionParam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Method implMethod : implMethods) {
|
||||
String implName = implMethod.getName();
|
||||
Class<?>[] implTypes = implMethod.getParameterTypes();
|
||||
int implLength = implTypes.length;
|
||||
/**
|
||||
* 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)
|
||||
* NOTE: does not null check
|
||||
*
|
||||
* @param implMethods methods from the implementation
|
||||
* @param origMethod original method from the interface
|
||||
*/
|
||||
private static
|
||||
Method getOverwriteMethodWithConnectionParam(final Method[] implMethods, final Method origMethod) {
|
||||
String origName = origMethod.getName();
|
||||
Class<?>[] origTypes = origMethod.getParameterTypes();
|
||||
int origLength = origTypes.length + 1;
|
||||
|
||||
if (origLength != implLength || !(origName.equals(implName))) {
|
||||
continue;
|
||||
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
|
||||
return implMethod;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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) {
|
||||
return implMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -103,30 +103,37 @@ interface RmiSerializationManager extends SerializationManager {
|
|||
Class<?> getRmiImpl(Class<?> iFace);
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server"
|
||||
* cannot access or create remote objects on this endpoint.
|
||||
* <p>
|
||||
* This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class.
|
||||
* <p>
|
||||
* There is additional overhead to using RMI.
|
||||
* <p>
|
||||
* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a
|
||||
* return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written.
|
||||
* <p>
|
||||
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
*/
|
||||
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass);
|
||||
RmiSerializationManager registerRmi(Class<?> ifaceClass);
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \
|
||||
* create remote objects on the "remote client".
|
||||
* <p>
|
||||
* Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)}
|
||||
* <p>
|
||||
* There is additional overhead to using RMI.
|
||||
* <p>
|
||||
* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a
|
||||
* return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written.
|
||||
* <p>
|
||||
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
* <p>
|
||||
* This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct
|
||||
* implementation type, so this side of the connection knows what to do with the proxy object.
|
||||
* <p>
|
||||
*
|
||||
* @throws IllegalArgumentException if the iface/impl have previously been overridden
|
||||
*/
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass);
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmi(Class<Iface> ifaceClass, Class<Impl> implClass);
|
||||
|
||||
/**
|
||||
* Gets the cached methods for the specified class ID
|
||||
|
|
|
@ -31,7 +31,6 @@ import org.slf4j.Logger;
|
|||
import com.esotericsoftware.kryo.ClassResolver;
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory;
|
||||
import com.esotericsoftware.kryo.factories.SerializerFactory;
|
||||
|
@ -55,6 +54,7 @@ import dorkbox.network.rmi.InvokeMethodResult;
|
|||
import dorkbox.network.rmi.InvokeMethodSerializer;
|
||||
import dorkbox.network.rmi.RemoteObjectSerializer;
|
||||
import dorkbox.network.rmi.RmiRegistration;
|
||||
import dorkbox.network.rmi.RmiRegistrationSerializer;
|
||||
import dorkbox.network.rmi.RmiUtils;
|
||||
import dorkbox.objectPool.ObjectPool;
|
||||
import dorkbox.objectPool.PoolableObject;
|
||||
|
@ -127,23 +127,12 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
class RemoteIfaceClass {
|
||||
private final Class<?> ifaceClass;
|
||||
|
||||
RemoteIfaceClass(final Class<?> ifaceClass) {
|
||||
this.ifaceClass = ifaceClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static
|
||||
class RemoteImplClass {
|
||||
class RmiClassSerializer {
|
||||
private final Class<?> ifaceClass;
|
||||
private final Class<?> implClass;
|
||||
|
||||
RemoteImplClass(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
RmiClassSerializer(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
this.ifaceClass = ifaceClass;
|
||||
this.implClass = implClass;
|
||||
}
|
||||
|
@ -171,8 +160,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
* <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.
|
||||
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and
|
||||
* {@link #registerRmi(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.
|
||||
|
@ -263,10 +252,7 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
// 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<?>> rmiKryoIdToImpl = new IntMap<Class<?>>();
|
||||
private final IntMap<Class<?>> rmiKryoIdToIface = new IntMap<Class<?>>();
|
||||
private RmiRegistrationSerializer rmiRegistrationSerializer;
|
||||
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
|
||||
|
@ -296,8 +282,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
* 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 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.
|
||||
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and
|
||||
* {@link #registerRmi(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.
|
||||
|
@ -325,6 +311,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
// we HAVE to pre-allocate the KRYOs
|
||||
KryoExtra<C> kryo = new KryoExtra<C>(Serialization.this);
|
||||
|
||||
boolean traceEnabled = logger.isTraceEnabled();
|
||||
|
||||
kryo.getFieldSerializerConfig()
|
||||
.setUseAsm(useAsm);
|
||||
kryo.setRegistrationRequired(registrationRequired);
|
||||
|
@ -333,13 +321,13 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
|
||||
if (usesRmi) {
|
||||
kryo.register(Class.class);
|
||||
kryo.register(RmiRegistration.class);
|
||||
kryo.register(RmiRegistration.class, rmiRegistrationSerializer);
|
||||
kryo.register(InvokeMethod.class, methodSerializer);
|
||||
kryo.register(Object[].class);
|
||||
|
||||
// This has to be for each kryo instance!
|
||||
InvocationResultSerializer resultSerializer = new InvocationResultSerializer(kryo);
|
||||
resultSerializer.removeField("objectID");
|
||||
resultSerializer.removeField("rmiObjectId");
|
||||
|
||||
kryo.register(InvokeMethodResult.class, resultSerializer);
|
||||
kryo.register(InvocationHandler.class, invocationSerializer);
|
||||
|
@ -349,63 +337,56 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
for (Object clazz : classesToRegister) {
|
||||
if (clazz instanceof Class) {
|
||||
Class aClass = (Class) clazz;
|
||||
kryo.register(aClass);
|
||||
int id = kryo.register(aClass).getId();
|
||||
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} -> {}", id, aClass.getName());
|
||||
}
|
||||
}
|
||||
else if (clazz instanceof ClassSerializer) {
|
||||
ClassSerializer classSerializer = (ClassSerializer) clazz;
|
||||
kryo.register(classSerializer.clazz, classSerializer.serializer);
|
||||
int id = kryo.register(classSerializer.clazz, classSerializer.serializer).getId();
|
||||
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} -> {} using {}", id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName());
|
||||
}
|
||||
}
|
||||
else if (clazz instanceof ClassSerializer1) {
|
||||
ClassSerializer1 classSerializer = (ClassSerializer1) clazz;
|
||||
kryo.register(classSerializer.clazz, classSerializer.id);
|
||||
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} (specified) -> {}", classSerializer.id, classSerializer.clazz);
|
||||
}
|
||||
}
|
||||
else if (clazz instanceof ClassSerializer2) {
|
||||
ClassSerializer2 classSerializer = (ClassSerializer2) clazz;
|
||||
kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id);
|
||||
}
|
||||
else 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;
|
||||
|
||||
// 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"
|
||||
// "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;
|
||||
|
||||
// check to see if the interface is already registered. If so, we override it with the implementation class
|
||||
EditableDefaultClassResolver classResolver = (EditableDefaultClassResolver) kryo.getClassResolver();
|
||||
Registration registration = classResolver.getRegistration(remoteImplClass.ifaceClass);
|
||||
if (registration != null) {
|
||||
// override it with the implementation
|
||||
int oldId = registration.getId();
|
||||
id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer, oldId).getId();
|
||||
|
||||
// delete the old one (since it shouldn't be stored anywhere)
|
||||
classResolver.deleteRegistrationType(remoteImplClass.ifaceClass);
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} (specified) -> {} using {}", classSerializer.id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName());
|
||||
}
|
||||
else {
|
||||
// we have a new registration.
|
||||
}
|
||||
else if (clazz instanceof RmiClassSerializer) {
|
||||
// "client" always sends kryo-ID (of iface) with RMI-ID (or command to create/get one)
|
||||
// "server" always responds with kryo-ID (of impl) with RMI-ID of remote object. When creating proxy object, "client" looks-up kryo-id of iface from impl
|
||||
|
||||
// registers the implementation, so that when it is WRITTEN, it becomes a "magic" proxy object
|
||||
id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
|
||||
RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz;
|
||||
|
||||
int id = kryo.register(remoteImplClass.ifaceClass, remoteObjectSerializer).getId();
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} -> RMI interface : {}", id, remoteImplClass.ifaceClass);
|
||||
}
|
||||
|
||||
// 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.
|
||||
rmiKryoIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
|
||||
rmiKryoIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
|
||||
// we only want to use an implementation class IFF we plan on having one, otherwise ignore it
|
||||
if (remoteImplClass.implClass != null) {
|
||||
// NOTE: as a "server" we must have the "remote object" serializer write out the INTERFACE CLASS ID so that the "client" (if it DOES NOT have an impl)
|
||||
// can read this as the correct class!
|
||||
int id2 = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
|
||||
if (traceEnabled) {
|
||||
logger.trace("Registering {} -> RMI implementation: {}", id2, remoteImplClass.implClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,7 +549,8 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
|
||||
methodSerializer = new InvokeMethodSerializer();
|
||||
invocationSerializer = new InvocationHandlerSerializer(logger);
|
||||
remoteObjectSerializer = new RemoteObjectSerializer();
|
||||
remoteObjectSerializer = new RemoteObjectSerializer(rmiImplToIface);
|
||||
rmiRegistrationSerializer = new RmiRegistrationSerializer();
|
||||
methodCache = new IntMap<CachedMethod[]>();
|
||||
|
||||
return true;
|
||||
|
@ -604,7 +586,12 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
}
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server"
|
||||
* cannot access or create remote objects on this endpoint.
|
||||
* <p>
|
||||
* This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class.
|
||||
* <p>
|
||||
* There is additional overhead to using RMI.
|
||||
* <p>
|
||||
* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a
|
||||
* return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written.
|
||||
|
@ -613,67 +600,120 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class) call.");
|
||||
return this;
|
||||
}
|
||||
|
||||
else if (!ifaceClass.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface.");
|
||||
}
|
||||
|
||||
usesRmi = true;
|
||||
classesToRegister.add(new RemoteIfaceClass(ifaceClass));
|
||||
|
||||
return this;
|
||||
RmiSerializationManager registerRmi(Class<?> ifaceClass) {
|
||||
return registerRmi(ifaceClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
|
||||
* Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \
|
||||
* create remote objects on the "remote client".
|
||||
* <p>
|
||||
* Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)}
|
||||
* <p>
|
||||
* There is additional overhead to using RMI.
|
||||
* <p>
|
||||
* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a
|
||||
* return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written.
|
||||
* <p>
|
||||
* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
* <p>
|
||||
* This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct
|
||||
* implementation type, so this side of the connection knows what to do with the proxy object.
|
||||
* <p>
|
||||
*
|
||||
* @throws IllegalArgumentException if the iface/impl have previously been overridden
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass) {
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmi(Class<Iface> ifaceClass, Class<Impl> implClass) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class, Class) call.");
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRmiImplementation(Class, Class) call.");
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!ifaceClass.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface.");
|
||||
}
|
||||
if (implClass.isInterface()) {
|
||||
if (implClass != null && implClass.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an interface for RMI implementations. It must be an implementation.");
|
||||
}
|
||||
|
||||
usesRmi = true;
|
||||
classesToRegister.add(new RemoteImplClass(ifaceClass, implClass));
|
||||
classesToRegister.add(new RmiClassSerializer(ifaceClass, implClass));
|
||||
|
||||
// this MUST BE UNIQUE otherwise unexpected and BAD things can happen.
|
||||
Class<?> a = this.rmiIfaceToImpl.put(ifaceClass, implClass);
|
||||
Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);
|
||||
// we only want to assign an implementation class IFF we plan on having one, otherwise ignore it
|
||||
if (implClass != null) {
|
||||
// rmiIfaceToImpl tells the "server" how to create a (requested) remote object
|
||||
// this MUST BE UNIQUE otherwise unexpected and BAD things can happen.
|
||||
Class<?> a = this.rmiIfaceToImpl.put(ifaceClass, implClass);
|
||||
Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);
|
||||
|
||||
if (a != null || b != null) {
|
||||
throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " +
|
||||
"because they have already been overridden by something else. It is critical that they are" +
|
||||
" both unique per JVM");
|
||||
if (a != null || b != null) {
|
||||
throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " +
|
||||
"because they have already been overridden by something else. It is critical that they are" +
|
||||
" both unique per JVM");
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(final Logger wireReadLogger, final Logger wireWriteLogger) {
|
||||
this.wireReadLogger = wireReadLogger;
|
||||
this.wireWriteLogger = wireWriteLogger;
|
||||
|
||||
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<C> kryo = null;
|
||||
try {
|
||||
kryo = kryoPool.take();
|
||||
|
||||
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 RmiClassSerializer) {
|
||||
// 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.
|
||||
RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz;
|
||||
int id = classResolver.getRegistration(remoteImplClass.ifaceClass)
|
||||
.getId();
|
||||
|
||||
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm,
|
||||
remoteImplClass.ifaceClass,
|
||||
remoteImplClass.implClass,
|
||||
id);
|
||||
methodCache.put(id, cachedMethods);
|
||||
}
|
||||
}
|
||||
|
||||
} 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.
|
||||
* <p>
|
||||
|
@ -780,81 +820,6 @@ class Serialization<C extends CryptoConnection> implements CryptoSerializationMa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(final Logger wireReadLogger, final Logger wireWriteLogger) {
|
||||
this.wireReadLogger = wireReadLogger;
|
||||
this.wireWriteLogger = wireWriteLogger;
|
||||
|
||||
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<C> kryo = null;
|
||||
try {
|
||||
kryo = kryoPool.take();
|
||||
|
||||
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(Serialization.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(Serialization.logger, kryo, useAsm,
|
||||
remoteImplClass.ifaceClass,
|
||||
remoteImplClass.implClass,
|
||||
id);
|
||||
methodCache.put(id, cachedMethods);
|
||||
}
|
||||
}
|
||||
|
||||
} 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.
|
||||
* <p>
|
||||
|
|
|
@ -222,11 +222,8 @@ class RmiGlobalTest extends BaseTest {
|
|||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// 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) - NOTICE: none of the super classes/interfaces are registered!
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
// NOTICE: none of the super classes/interfaces are registered!
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
|
@ -286,11 +283,8 @@ class RmiGlobalTest extends BaseTest {
|
|||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
// NOTICE: none of the super classes/interfaces are registered!
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
|
@ -340,7 +334,8 @@ class RmiGlobalTest extends BaseTest {
|
|||
}
|
||||
});
|
||||
|
||||
client.connect(5000);
|
||||
// client.connect(5000);
|
||||
client.connect(0);
|
||||
waitForThreads();
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
|
|||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
// @Test // NOTE: change final to test!
|
||||
// @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test!
|
||||
public
|
||||
void rmiNetwork() throws SecurityException, IOException, InterruptedException {
|
||||
rmi(new Config() {
|
||||
|
@ -60,7 +60,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
|
|||
Thread.sleep(2000L);
|
||||
}
|
||||
|
||||
// @Test // NOTE: change final to test!
|
||||
// @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test!
|
||||
public
|
||||
void rmiLocal() throws SecurityException, IOException, InterruptedException {
|
||||
rmi(new Config() {
|
||||
|
@ -80,7 +80,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
|
|||
|
||||
public
|
||||
void rmi(final Config config) throws SecurityException, IOException {
|
||||
// NOTE: change final to test!
|
||||
// NOTE: remove final from RmiBridge.INVALID_RMI to test!
|
||||
// RmiBridge.INVALID_RMI = 4;
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
|
@ -88,13 +88,7 @@ class RmiObjectIdExhaustionTest extends BaseTest {
|
|||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
// for Server -> Client RMI (ID 2)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
|
@ -102,18 +96,16 @@ class RmiObjectIdExhaustionTest extends BaseTest {
|
|||
addEndPoint(server);
|
||||
server.bind(false);
|
||||
|
||||
|
||||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
// for Server -> Client RMI (ID 2)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
|
|
|
@ -106,9 +106,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
|
||||
final boolean isUDP = configuration.udpPort > 0;
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
|
@ -138,9 +138,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
|||
configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
|
|
@ -90,9 +90,9 @@ class RmiSendObjectTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
||||
|
||||
|
@ -124,9 +124,9 @@ class RmiSendObjectTest extends BaseTest {
|
|||
configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
||||
Client client = new Client(configuration);
|
||||
|
|
|
@ -171,6 +171,7 @@ class RmiTest extends BaseTest {
|
|||
public static
|
||||
void register(dorkbox.network.serialization.CryptoSerializationManager manager) {
|
||||
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
|
||||
manager.register(TestCow.class);
|
||||
manager.register(MessageWithTestCow.class);
|
||||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
@ -217,14 +218,14 @@ class RmiTest extends BaseTest {
|
|||
Configuration configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
// for Server -> Client RMI (ID 2)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
// configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
|
@ -274,14 +275,15 @@ class RmiTest extends BaseTest {
|
|||
configuration = new Configuration();
|
||||
config.apply(configuration);
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Client -> Server RMI (ID 1)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
// the 'TestCow' object lives on the 'Server'. The 'Client' accesses remote methods on it
|
||||
// configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
// for Server -> Client RMI (ID 2)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
// // for Server -> Client RMI (ID 2)
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
|
|
|
@ -37,8 +37,8 @@ class TestClient
|
|||
|
||||
// rootLogger.setLevel(Level.OFF);
|
||||
|
||||
rootLogger.setLevel(Level.DEBUG);
|
||||
// rootLogger.setLevel(Level.TRACE);
|
||||
// rootLogger.setLevel(Level.DEBUG);
|
||||
rootLogger.setLevel(Level.TRACE);
|
||||
// rootLogger.setLevel(Level.ALL);
|
||||
|
||||
|
||||
|
@ -78,14 +78,15 @@ class TestClient
|
|||
configuration.udpPort = 2001;
|
||||
configuration.host = "localhost";
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
configuration.serialization.registerRmi(TestCow.class);
|
||||
|
||||
|
||||
try {
|
||||
final Client client = new Client(configuration);
|
||||
// client.setIdleTimeout(0);
|
||||
client.disableRemoteKeyValidation();
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
client.listeners()
|
||||
.add(new dorkbox.network.connection.Listener.OnConnected<Connection>() {
|
||||
|
@ -121,7 +122,7 @@ class TestClient
|
|||
}
|
||||
});
|
||||
|
||||
client.connect(3330);
|
||||
client.connect(0);
|
||||
|
||||
client.waitForShutdown();
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -21,17 +21,18 @@ class TestServer
|
|||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
|
||||
configuration.serialization = Serialization.DEFAULT();
|
||||
configuration.serialization = Serialization.DEFAULT(true, true, false, null);
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class);
|
||||
|
||||
Server server = null;
|
||||
try {
|
||||
server = new Server(configuration);
|
||||
server.disableRemoteKeyValidation();
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// server.setIdleTimeout(0);
|
||||
server.setIdleTimeout(0);
|
||||
server.bind(true);
|
||||
}}
|
||||
|
|
Loading…
Reference in New Issue
Block a user