diff --git a/src/dorkbox/network/connection/ClassObject.java b/src/dorkbox/network/connection/ClassObject.java deleted file mode 100644 index 7f1991aa..00000000 --- a/src/dorkbox/network/connection/ClassObject.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.connection; - -class ClassObject { - final Class clazz; - final Object object; - - public ClassObject(final Class implementationClass, final Object remotePrimaryObject) { - clazz = implementationClass; - object = remotePrimaryObject; - } -} diff --git a/src/dorkbox/network/connection/ConnectionImpl.java b/src/dorkbox/network/connection/ConnectionImpl.java index 316d2c4b..d3f73d0d 100644 --- a/src/dorkbox/network/connection/ConnectionImpl.java +++ b/src/dorkbox/network/connection/ConnectionImpl.java @@ -17,6 +17,7 @@ package dorkbox.network.connection; import java.io.IOException; import java.lang.reflect.Field; +import java.util.AbstractMap; import java.util.LinkedList; import java.util.Map; import java.util.WeakHashMap; @@ -42,7 +43,8 @@ import dorkbox.network.rmi.RemoteObjectCallback; import dorkbox.network.rmi.Rmi; import dorkbox.network.rmi.RmiBridge; import dorkbox.network.rmi.RmiRegistration; -import dorkbox.network.serialization.RmiSerializationManager; +import dorkbox.network.serialization.CryptoSerializationManager; +import dorkbox.util.ClassHelper; import dorkbox.util.collections.IntMap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; @@ -110,7 +112,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn private final RmiBridge rmiBridge; private final Map proxyIdCache = new WeakHashMap(8); private final IntMap rmiRegistrationCallbacks = new IntMap(); - private int rmiRegistrationID = 0; // protected by synchronized (rmiRegistrationCallbacks) + private int rmiCallbackId = 0; // protected by synchronized (rmiRegistrationCallbacks) /** * All of the parameters can be null, when metaChannel wants to get the base class type @@ -902,11 +904,14 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn // // RMI methods // + // + + @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"}) @Override public final - void getRemoteObject(final Class interfaceClass, final RemoteObjectCallback callback) { + void createRemoteObject(final Class interfaceClass, final RemoteObjectCallback callback) { if (!interfaceClass.isInterface()) { throw new IllegalArgumentException("Cannot create a proxy for RMI access. It must be an interface."); } @@ -914,9 +919,9 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn RmiRegistration message; synchronized (rmiRegistrationCallbacks) { - int nextRmiID = rmiRegistrationID++; - rmiRegistrationCallbacks.put(nextRmiID, callback); - message = new RmiRegistration(interfaceClass, nextRmiID); + int nextRmiCallbackId = rmiCallbackId++; + rmiRegistrationCallbacks.put(nextRmiCallbackId, callback); + message = new RmiRegistration(interfaceClass, RmiBridge.INVALID_RMI, nextRmiCallbackId); } // We use a callback to notify us when the object is ready. We can't "create this on the fly" because we @@ -932,10 +937,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn void getRemoteObject(final int objectId, final RemoteObjectCallback callback) { RmiRegistration message; + Class iFaceClass = ClassHelper.getGenericParameterAsClassForSuperClass(RemoteObjectCallback.class, callback.getClass(), 0); + synchronized (rmiRegistrationCallbacks) { - int nextRmiID = rmiRegistrationID++; - rmiRegistrationCallbacks.put(nextRmiID, callback); - message = new RmiRegistration(objectId, nextRmiID); + int nextRmiCallbackId = rmiCallbackId++; + rmiRegistrationCallbacks.put(nextRmiCallbackId, callback); + message = new RmiRegistration(iFaceClass, objectId, nextRmiCallbackId); } // We use a callback to notify us when the object is ready. We can't "create this on the fly" because we @@ -945,146 +952,129 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn TCP(message).flush(); } - private - void collectRmiFields(final RmiBridge rmiBridge, - final LinkedList classesToCheck, final ClassObject remoteClassObject, final Field[] fields) { + // default false + public static boolean ENABLE_PROXY_OBJECTS = true; - } + /** + * For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically. + * For local connections, we have to switch it appropriately in the LocalRmiProxy + * + * @param implementationClass + * @param callbackId + * @return + */ + public final + RmiRegistration createNewRmiObject(final Class interfaceClass, final Class implementationClass, final int callbackId) { - final - void registerInternal(final ConnectionImpl connection, final RmiRegistration remoteRegistration) { - final Class interfaceClass = remoteRegistration.interfaceClass; - final int rmiID = remoteRegistration.rmiID; + CryptoSerializationManager manager = getEndPoint().serializationManager; - if (interfaceClass != null) { - // THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation) - // - // CREATE a new ID, and register the ID and new object (must create a new one) in the object maps + KryoExtra kryo = null; + Object object = null; + int rmiId = 0; - // the interface class kryo ID == implementation class kryo ID, so they switcheroo automatically. - final Class implementationClass = interfaceClass; + try { + kryo = manager.takeKryo(); + // this is what creates a new instance of the impl class, and stores it as an ID. + object = kryo.newInstance(implementationClass); - final RmiSerializationManager manager = getEndPoint().serializationManager; - - KryoExtra kryo = null; - final Object remotePrimaryObject; - try { - kryo = manager.takeKryo(); - // this is what creates a new instance of the impl class, and stores it as an ID. - remotePrimaryObject = kryo.newInstance(implementationClass); - } catch (Exception e) { - logger.error("Error creating RMI class " + implementationClass, e); - connection.TCP(new RmiRegistration(rmiID)) - .flush(); - return; - } finally { - if (kryo != null) { - // we use kryo to create a new instance - so only return it on error or when it's done creating a new instance - manager.returnKryo(kryo); - } - } + rmiId = rmiBridge.nextObjectId(); + rmiBridge.register(rmiId, object); - try { - rmiBridge.register(rmiBridge.nextObjectId(), remotePrimaryObject); + + // the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI objects + LinkedList, Object>> classesToCheck = new LinkedList, Object>>(); + classesToCheck.add(new AbstractMap.SimpleEntry, Object>(implementationClass, object)); - // the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI - LinkedList classesToCheck = new LinkedList(); - classesToCheck.add(new ClassObject(implementationClass, remotePrimaryObject)); + Map.Entry, Object> remoteClassObject; + while (!classesToCheck.isEmpty()) { + remoteClassObject = classesToCheck.removeFirst(); - ClassObject remoteClassObject; - while (!classesToCheck.isEmpty()) { - remoteClassObject = classesToCheck.removeFirst(); + // we have to check the IMPLEMENTATION for any additional fields that will have proxy information. + // we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible (public + private). + for (Field field : remoteClassObject.getKey() + .getDeclaredFields()) { + if (field.getAnnotation(Rmi.class) != null) { + final Class type = field.getType(); - // we have to check the IMPLEMENTATION for any additional fields that will have proxy information. - // we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible. - for (Field field : remoteClassObject.clazz.getDeclaredFields()) { - if (field.getAnnotation(Rmi.class) != null) { - final Class type = field.getType(); - - if (!type.isInterface()) { - // the type must be an interface, otherwise RMI cannot create a proxy object - logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!", remoteClassObject.clazz, field.getName()); - continue; - } + if (!type.isInterface()) { + // the type must be an interface, otherwise RMI cannot create a proxy object + logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!", + remoteClassObject.getKey(), + field.getName()); + continue; + } - boolean prev = field.isAccessible(); - field.setAccessible(true); - final Object o; - try { - o = field.get(remoteClassObject.object); + boolean prev = field.isAccessible(); + field.setAccessible(true); + final Object o; + try { + o = field.get(remoteClassObject.getValue()); - rmiBridge.register(rmiBridge.nextObjectId(), o); - classesToCheck.add(new ClassObject(type, o)); - } catch (IllegalAccessException e) { - logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.clazz, field.getName(), e); - } finally { - field.setAccessible(prev); - } + rmiBridge.register(rmiBridge.nextObjectId(), o); + classesToCheck.add(new AbstractMap.SimpleEntry, Object>(type, o)); + } catch (IllegalAccessException e) { + logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e); + } finally { + field.setAccessible(prev); } } - - - // have to check the object hierarchy as well - Class superclass = remoteClassObject.clazz.getSuperclass(); - if (superclass != null && superclass != Object.class) { - classesToCheck.add(new ClassObject(superclass, remoteClassObject.object)); - } } - connection.TCP(new RmiRegistration(remotePrimaryObject, rmiID)).flush(); - } catch (Exception e) { - logger.error("Error registering RMI class " + implementationClass, e); - connection.TCP(new RmiRegistration(rmiID)).flush(); + + // have to check the object hierarchy as well + Class superclass = remoteClassObject.getKey() + .getSuperclass(); + if (superclass != null && superclass != Object.class) { + classesToCheck.add(new AbstractMap.SimpleEntry, Object>(superclass, remoteClassObject.getValue())); + } + } + } catch (Exception e) { + logger.error("Error registering RMI class " + implementationClass, e); + } finally { + if (kryo != null) { + // we use kryo to create a new instance - so only return it on error or when it's done creating a new instance + manager.returnKryo(kryo); } } - else if (remoteRegistration.remoteObjectId > RmiBridge.INVALID_RMI) { - // THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist) - // - // GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection). - Object object = getImplementationObject(remoteRegistration.remoteObjectId); - if (object != null) { - connection.TCP(new RmiRegistration(object, rmiID)).flush(); - } else { - connection.TCP(new RmiRegistration(rmiID)).flush(); - } + return new RmiRegistration(interfaceClass, rmiId, callbackId, object); + } + + public final + RmiRegistration getExistingRmiObject(final Class interfaceClass, final int rmiId, final int callbackId) { + Object object = getImplementationObject(rmiId); + + return new RmiRegistration(interfaceClass, rmiId, callbackId, object); + } + + public final + void runRmiCallback(final Class interfaceClass, final int callbackId, final Object remoteObject) { + RemoteObjectCallback callback; + synchronized (rmiRegistrationCallbacks) { + callback = rmiRegistrationCallbacks.remove(callbackId); } - else { - // THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client. - // this will be null if there was an error - Object remoteObject = remoteRegistration.remoteObject; - - boolean noMoreRmiRemaining ; - RemoteObjectCallback callback; - - synchronized (rmiRegistrationCallbacks) { - callback = rmiRegistrationCallbacks.remove(remoteRegistration.rmiID); - } - - try { - //noinspection unchecked - callback.created(remoteObject); - } catch (Exception e) { - logger.error("Error getting remote object " + remoteObject.getClass() + ", ID: " + rmiID, e); - } + try { + //noinspection unchecked + callback.created(remoteObject); + } catch (Exception e) { + logger.error("Error getting or creating the remote object " + interfaceClass, e); } } /** - * Used by RMI + * Used by RMI by the LOCAL side when setting up the to fetch an object for the REMOTE side * - * @return the registered ID for a specific object. This is used by the "client" side when setting up the to fetch an object for the - * "service" side for RMI + * @return the registered ID for a specific object. */ @Override public int getRegisteredId(final T object) { - // always check local before checking global, because less contention on the synchronization + // always check global before checking local, because less contention on the synchronization RmiBridge globalRmiBridge = endPoint.globalRmiBridge; if (globalRmiBridge == null) { @@ -1100,7 +1090,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn } /** - * Used by RMI for the CLIENT side, to get the proxy object as an interface + * Used by RMI for the LOCAL side, to get the proxy object as an interface * * @param objectID is the RMI object ID * @param iFace must be the interface the proxy will bind to diff --git a/src/dorkbox/network/connection/RegisterRmiLocalHandler.java b/src/dorkbox/network/connection/RegisterRmiLocalHandler.java new file mode 100644 index 00000000..f6c9e9ae --- /dev/null +++ b/src/dorkbox/network/connection/RegisterRmiLocalHandler.java @@ -0,0 +1,419 @@ +/* + * Copyright 2018 dorkbox, llc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.network.connection; + +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.util.IdentityMap; + +import dorkbox.network.rmi.CachedMethod; +import dorkbox.network.rmi.InvokeMethod; +import dorkbox.network.rmi.RemoteObject; +import dorkbox.network.rmi.RmiBridge; +import dorkbox.network.rmi.RmiMessages; +import dorkbox.network.rmi.RmiProxyHandler; +import dorkbox.network.rmi.RmiRegistration; +import dorkbox.network.serialization.CryptoSerializationManager; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; + +/** + * This is for a local-connection (same-JVM) RMI method invocation + * + * Uses the "single writer principle" for fast access, but disregards 'single writer' for field cache, because duplicates are OK + *

+ * This is for a LOCAL connection (same-JVM) + */ +public +class RegisterRmiLocalHandler extends MessageToMessageDecoder { + private static final boolean ENABLE_PROXY_OBJECTS = ConnectionImpl.ENABLE_PROXY_OBJECTS; + private static final Field[] NO_REMOTE_FIELDS = new Field[0]; + + // private static final AtomicReferenceFieldUpdater rmiFieldsREF = AtomicReferenceFieldUpdater.newUpdater( + // RegisterRmiLocalHandler.class, + // IdentityMap.class, + // "fieldCache"); + + private static final AtomicReferenceFieldUpdater implToProxyREF = AtomicReferenceFieldUpdater.newUpdater( + RegisterRmiLocalHandler.class, + IdentityMap.class, + "implToProxy"); + + private static final AtomicReferenceFieldUpdater remoteObjectREF = AtomicReferenceFieldUpdater.newUpdater( + RegisterRmiLocalHandler.class, + IdentityMap.class, + "objectHasRemoteObjects"); + + // private volatile IdentityMap, Field[]> fieldCache = new IdentityMap, Field[]>(); + private volatile IdentityMap implToProxy = new IdentityMap(); + private volatile IdentityMap objectHasRemoteObjects = new IdentityMap(); + + + public + RegisterRmiLocalHandler() { + } + + @Override + protected + void decode(final ChannelHandlerContext context, final Object msg, final List out) throws Exception { + ConnectionImpl connection = (ConnectionImpl) context.pipeline() + .last(); + + if (msg instanceof RmiRegistration) { + receivedRegistration(connection, (RmiRegistration) msg); + } + else { + if (msg instanceof InvokeMethod) { + InvokeMethod invokeMethod = (InvokeMethod) msg; + int methodClassID = invokeMethod.cachedMethod.methodClassID; + int methodIndex = invokeMethod.cachedMethod.methodIndex; + // have to replace the cached methods with the correct (remote) version, otherwise the wrong methods CAN BE invoked. + + CryptoSerializationManager serialization = connection.getEndPoint() + .getSerialization(); + + + CachedMethod cachedMethod; + try { + cachedMethod = serialization.getMethods(methodClassID)[methodIndex]; + } catch (Exception ex) { + String errorMessage; + KryoExtra kryo = null; + try { + kryo = serialization.takeKryo(); + + Class methodClass = kryo.getRegistration(methodClassID) + .getType(); + + errorMessage = "Invalid method index " + methodIndex + " for class: " + methodClass.getName(); + } finally { + serialization.returnKryo(kryo); + } + + throw new KryoException(errorMessage); + } + + + Object[] args; + Serializer[] serializers = cachedMethod.serializers; + + int argStartIndex; + + if (cachedMethod.overriddenMethod) { + // did we override our cached method? This is not common. + // this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0) + argStartIndex = 1; + + args = new Object[serializers.length + 1]; + args[0] = connection; + } + else { + argStartIndex = 0; + args = new Object[serializers.length]; + } + + for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) { + args[j] = invokeMethod.args[i]; + } + + // overwrite the invoke method fields with UPDATED versions that have the correct (remote side) implementation/args + invokeMethod.cachedMethod = cachedMethod; + invokeMethod.args = args; + } + + receivedNormal(connection, msg, out); + } + } + + private + void receivedNormal(final ConnectionImpl connection, final Object msg, final List out) { + // else, this was "just a local message" + + if (msg instanceof RmiMessages) { + // don't even process these message types + out.add(msg); + return; + } + + // because we NORMALLY pass around just the object (there is no serialization going on...) we have to explicitly check to see + // if this object, or any of it's fields MIGHT HAVE BEEN an RMI Proxy (or should be on), and switcheroo it here. + // NORMALLY this is automatic since the kryo IDs on each side point to the "correct object" for serialization, but here we don't do that. + + // maybe this object is supposed to switch to a proxy object?? (note: we cannot send proxy objects over local/network connections) + + @SuppressWarnings("unchecked") + IdentityMap implToProxy = implToProxyREF.get(this); + IdentityMap objectHasRemoteObjects = remoteObjectREF.get(this); + + + Object proxy = implToProxy.get(msg); + if (proxy != null) { + // we have a proxy object. nothing left to do. + out.add(proxy); + return; + } + + + Class messageClass = msg.getClass(); + + // are there any fields of this message class that COULD contain remote object fields? (NOTE: not RMI fields yet...) + final Field[] remoteObjectFields = objectHasRemoteObjects.get(messageClass); + if (remoteObjectFields == null) { + // maybe one of it's fields is a proxy object? + + // we cache the fields that have to be replaced, so subsequent invocations are significantly more preformat + final ArrayList fields = new ArrayList(); + + // we have to walk the hierarchy of this object to check ALL fields, public and private, using getDeclaredFields() + while (messageClass != Object.class) { + // this will get ALL fields that are + for (Field field : messageClass.getDeclaredFields()) { + final Class type = field.getType(); + + if (type.isInterface()) { + boolean prev = field.isAccessible(); + final Object o; + try { + field.setAccessible(true); + o = field.get(msg); + + if (o instanceof RemoteObject) { + RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o); + + int id = handler.objectID; + field.set(msg, connection.getImplementationObject(id)); + fields.add(field); + } + else { + // is a field supposed to be a proxy? + proxy = implToProxy.get(o); + if (proxy != null) { + field.set(msg, proxy); + fields.add(field); + } + } + + } catch (IllegalAccessException e) { + e.printStackTrace(); + // logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e); + } finally { + field.setAccessible(prev); + } + } + } + + messageClass = messageClass.getSuperclass(); + } + + Field[] array; + if (fields.isEmpty()) { + // no need to ever process this class again. + array = NO_REMOTE_FIELDS; + } + else { + array = fields.toArray(new Field[fields.size()]); + } + + //noinspection SynchronizeOnNonFinalField + synchronized (objectHasRemoteObjects) { + // i know what I'm doing. This must be synchronized. + objectHasRemoteObjects.put(messageClass, array); + } + } + else if (remoteObjectFields != NO_REMOTE_FIELDS) { + // quickly replace objects as necessary + + for (Field field : remoteObjectFields) { + boolean prev = field.isAccessible(); + final Object o; + try { + field.setAccessible(true); + o = field.get(msg); + + if (o instanceof RemoteObject) { + RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(o); + + int id = handler.objectID; + field.set(msg, connection.getImplementationObject(id)); + } + else { + // is a field supposed to be a proxy? + proxy = implToProxy.get(o); + if (proxy != null) { + field.set(msg, proxy); + } + } + + } catch (IllegalAccessException e) { + e.printStackTrace(); + // logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.getKey(), field.getName(), e); + } finally { + field.setAccessible(prev); + } + } + } + + out.add(msg); + } + + + private + void receivedRegistration(final ConnectionImpl connection, final RmiRegistration registration) { + // manage creating/getting/notifying this RMI object + + // these fields are ALWAYS present! + final Class interfaceClass = registration.interfaceClass; + final int callbackId = registration.callbackId; + if (registration.isRequest) { + // Check if we are creating a new REMOTE object. This check is always first. + if (registration.rmiId == RmiBridge.INVALID_RMI) { + // THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation) + // + // CREATE a new ID, and register the ID and new object (must create a new one) in the object maps + + + // have to convert the iFace -> Impl + EndPointBase endPoint = connection.getEndPoint(); + CryptoSerializationManager serialization = endPoint.getSerialization(); + + Class rmiImpl = serialization.getRmiImpl(registration.interfaceClass); + + RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, rmiImpl, callbackId); + connection.TCP(registrationResult) + .flush(); + } + + // Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object + else { + // THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist) + // + // GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection). + RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId); + + connection.TCP(registrationResult) + .flush(); + } + } + else { + // this is the response. + // THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client. + + + // on "local" connections (as opposed to "network" connections), the objects ARE NOT serialized, so we never + // generate a proxy via the rmiID - so the LocalRmiProxy (in this use case) overwrites the remoteObject with a proxy object + // if we are the response, we want to create a proxy object instead of just passing the ACTUAL object. + // On the "network" (RemoteObjectSerializer.java) stack, this process is automatic -- and here we have to mimic this behavior. + + // has to be 'registration.remoteObject' because we use it later on + if (ENABLE_PROXY_OBJECTS && registration.remoteObject != null) { + // override the implementation object with the proxy. This is required because RMI must be the same between "network" and "local" + // connections -- even if this "slows down" the speed/performance of what "local" connections offer. + RemoteObject proxyObject = connection.getProxyObject(registration.rmiId, interfaceClass); + + // have to save A and B so we can correctly switch as necessary + //noinspection SynchronizeOnNonFinalField + synchronized (implToProxy) { + // i know what I'm doing. This must be synchronized. + implToProxy.put(registration.remoteObject, proxyObject); + } + + connection.runRmiCallback(interfaceClass, callbackId, proxyObject); + } + else { + connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject); + } + } + } + + // private + // LocalRmiClassEncoder replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class implClass) { + // Field[] rmiFields = fieldCache.get(implClass); + // int length = rmiFields.length; + // + // Object rmiObject = null; + // int[] rmiFieldIds = new int[length]; + // for (int i = 0; i < length; i++) { + // Field field = rmiFields[i]; + // + // // if it's an RMI object we want to write out a proxy object in the field instead of the actual object + // try { + // rmiObject = field.get(object); + // } catch (IllegalAccessException e) { + // e.printStackTrace(); + // } + // + // if (rmiObject == null) { + // rmiFieldIds[i] = 0; // 0 means it was null + // } + // + // final Map localWeakCache = objectThreadLocals.get(); + // + // Integer id = localWeakCache.get(rmiObject); + // if (id == null) { + // // duplicates are fine, as they represent the same object (as specified by the ID) on the remote side. + // int registeredId = connection.getRegisteredId(rmiObject); + // rmiFieldIds[i] = registeredId; + // localWeakCache.put(rmiObject, registeredId); + // } + // else { + // rmiFieldIds[i] = id; + // } + // } + // + // LocalRmiClassEncoder localRmiClassEncoder = new LocalRmiClassEncoder(); + // localRmiClassEncoder.rmiObject = object; + // localRmiClassEncoder.rmiFieldIds = rmiFieldIds; + // + // return localRmiClassEncoder; + // } + + // Field[] getFields(final Class clazz) { + // // duplicates are OK, because they will contain the same information, so we DO NOT care about single writers + // + // //noinspection unchecked + // final IdentityMap, Field[]> identityMap = rmiFieldsREF.get(this); + // + // + // Field[] rmiFields = identityMap.get(clazz); + // if (rmiFields != null) { + // return rmiFields; + // } + // + // final ArrayList fields = new ArrayList(); + // + // for (Field field : clazz.getDeclaredFields()) { + // if (field.getAnnotation(Rmi.class) != null) { + // fields.add(field); + // } + // } + // + // + // rmiFields = new Field[fields.size()]; + // fields.toArray(rmiFields); + // + // // save in cache + // fieldCache.put(clazz, rmiFields); + // rmiFieldsREF.lazySet(this, fieldCache); + // + // return rmiFields; + // } +} diff --git a/src/dorkbox/network/connection/RegisterRmiNetworkHandler.java b/src/dorkbox/network/connection/RegisterRmiNetworkHandler.java new file mode 100644 index 00000000..b87d0f76 --- /dev/null +++ b/src/dorkbox/network/connection/RegisterRmiNetworkHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.network.connection; + +import dorkbox.network.rmi.RmiBridge; +import dorkbox.network.rmi.RmiRegistration; + +class RegisterRmiNetworkHandler implements Listener.OnMessageReceived { + + RegisterRmiNetworkHandler() { + } + + @Override + public + void received(final ConnectionImpl connection, final RmiRegistration registration) { + // manage creating/getting/notifying this RMI object + + // these fields are ALWAYS present! + final Class interfaceClass = registration.interfaceClass; + final int callbackId = registration.callbackId; + + + if (registration.isRequest) { + // Check if we are creating a new REMOTE object. This check is always first. + if (registration.rmiId == RmiBridge.INVALID_RMI) { + // THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation) + // + // CREATE a new ID, and register the ID and new object (must create a new one) in the object maps + + // For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically. + RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId); + connection.TCP(registrationResult).flush(); + } + + // Check if we are getting an already existing REMOTE object. This check is always AFTER the check to create a new object + else { + // THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist) + // + // GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection). + RmiRegistration registrationResult = connection.getExistingRmiObject(interfaceClass, registration.rmiId, callbackId); + connection.TCP(registrationResult).flush(); + } + } + else { + // this is the response. + // THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()' This can be Server or Client. + connection.runRmiCallback(interfaceClass, callbackId, registration.remoteObject); + } + } +} diff --git a/src/dorkbox/network/connection/RegisterRmiSystemListener.java b/src/dorkbox/network/connection/RegisterRmiSystemListener.java deleted file mode 100644 index a293d1a1..00000000 --- a/src/dorkbox/network/connection/RegisterRmiSystemListener.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.connection; - -import dorkbox.network.rmi.RmiRegistration; - -class RegisterRmiSystemListener implements Listener.OnMessageReceived { - - RegisterRmiSystemListener() { - } - - @Override - public - void received(final ConnectionImpl connection, final RmiRegistration registerClass) { - // register this into the RmiBridge - connection.registerInternal(connection, registerClass); - } -} diff --git a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandler.java b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandler.java index 7abae3b7..b8f67e2d 100644 --- a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandler.java +++ b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandler.java @@ -18,23 +18,18 @@ package dorkbox.network.connection.registration.local; import static dorkbox.network.connection.EndPointBase.maxShutdownWaitTimeInMilliSeconds; import dorkbox.network.connection.Connection; +import dorkbox.network.connection.RegisterRmiLocalHandler; import dorkbox.network.connection.RegistrationWrapper; import dorkbox.network.connection.registration.MetaChannel; import dorkbox.network.connection.registration.RegistrationHandler; -import dorkbox.network.pipeline.rmi.LocalRmiDecoder; -import dorkbox.network.pipeline.rmi.LocalRmiEncoder; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; public abstract class RegistrationLocalHandler extends RegistrationHandler { - protected static final String LOCAL_RMI_ENCODER = "localRmiEncoder"; - protected static final String LOCAL_RMI_DECODER = "localRmiDecoder"; + static final String LOCAL_RMI_HANDLER = "localRmiHandler"; + final RegisterRmiLocalHandler rmiLocalHandler = new RegisterRmiLocalHandler(); - protected final LocalRmiEncoder encoder = new LocalRmiEncoder(); - protected final LocalRmiDecoder decoder = new LocalRmiDecoder(); - - public RegistrationLocalHandler(String name, RegistrationWrapper registrationWrapper) { super(name, registrationWrapper); } diff --git a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerClient.java b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerClient.java index 44dbbafb..21b975d7 100644 --- a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerClient.java +++ b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerClient.java @@ -84,13 +84,7 @@ class RegistrationLocalHandlerClient extends RegistrationL /////////////////////// // DECODE (or upstream) /////////////////////// - pipeline.addFirst(LOCAL_RMI_ENCODER, decoder); - - - ///////////////////////// - // ENCODE (or downstream) - ///////////////////////// - pipeline.addFirst(LOCAL_RMI_DECODER, encoder); + pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler); } // have to setup connection handler diff --git a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerServer.java b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerServer.java index 429caac1..5c93a0b2 100644 --- a/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerServer.java +++ b/src/dorkbox/network/connection/registration/local/RegistrationLocalHandlerServer.java @@ -83,13 +83,7 @@ class RegistrationLocalHandlerServer extends RegistrationL /////////////////////// // DECODE (or upstream) /////////////////////// - pipeline.addFirst(LOCAL_RMI_ENCODER, decoder); - - - ///////////////////////// - // ENCODE (or downstream) - ///////////////////////// - pipeline.addFirst(LOCAL_RMI_DECODER, encoder); + pipeline.addFirst(LOCAL_RMI_HANDLER, rmiLocalHandler); } // have to setup connection handler diff --git a/src/dorkbox/network/pipeline/rmi/LocalRmiClassEncoder.java b/src/dorkbox/network/pipeline/rmi/LocalRmiClassEncoder.java deleted file mode 100644 index 23b97e44..00000000 --- a/src/dorkbox/network/pipeline/rmi/LocalRmiClassEncoder.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.pipeline.rmi; - -/** - * This is for a LOCAL connection (same-JVM) - */ -class LocalRmiClassEncoder { - public Object rmiObject; - - // these are the "in order" rmi ID's for the fields of 'rmiObject' - public int[] rmiFieldIds; - - public - LocalRmiClassEncoder() { - } -} diff --git a/src/dorkbox/network/pipeline/rmi/LocalRmiDecoder.java b/src/dorkbox/network/pipeline/rmi/LocalRmiDecoder.java deleted file mode 100644 index 905a00cf..00000000 --- a/src/dorkbox/network/pipeline/rmi/LocalRmiDecoder.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.pipeline.rmi; - -import java.lang.reflect.Field; -import java.util.List; - -import dorkbox.network.connection.ConnectionImpl; -import dorkbox.network.rmi.RemoteObject; -import dorkbox.network.serialization.CryptoSerializationManager; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; - -/** - * This is for a LOCAL connection (same-JVM) - */ -public -class LocalRmiDecoder extends MessageToMessageDecoder { - - private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE(); - - public - LocalRmiDecoder() { - super(); - } - - @Override - protected - void decode(final ChannelHandlerContext context, final Object msg, final List out) throws Exception { - if (msg instanceof LocalRmiClassEncoder) { - LocalRmiClassEncoder encoded = (LocalRmiClassEncoder) msg; - - final Object messageObject = encoded.rmiObject; - final int[] rmiFieldIds = encoded.rmiFieldIds; - - final Class messageClass = messageObject.getClass(); - ConnectionImpl connection = (ConnectionImpl) context.pipeline() - .last(); - - Object localRmiObject = null; - Field field; - int registeredId; - final Field[] rmiFields = fieldCache.get(messageClass); - for (int i = 0; i < rmiFields.length; i++) { - field = rmiFields[i]; - registeredId = rmiFieldIds[i]; - - if (registeredId == 0) { - // the field was null/ignore - } else { - // if it's an RMI object we want to write out a proxy object in the field instead of the actual object - try { - localRmiObject = field.get(messageObject); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - if (localRmiObject == null) { - throw new RuntimeException("Unable to get RMI interface object for RMI implementation"); - } - - - CryptoSerializationManager serialization = connection.getEndPoint() - .getSerialization(); - - // TODO: see what's up with this. - final Class iface = serialization.getRmiIface(localRmiObject.getClass()); - if (iface == null) { - throw new RuntimeException("Unable to get interface for RMI implementation"); - } - - RemoteObject remoteObject = connection.getProxyObject(registeredId, iface); - field.set(messageObject, remoteObject); - } - } - - out.add(messageObject); - } else { - out.add(msg); - } - } -} diff --git a/src/dorkbox/network/pipeline/rmi/LocalRmiEncoder.java b/src/dorkbox/network/pipeline/rmi/LocalRmiEncoder.java deleted file mode 100644 index de6c77c0..00000000 --- a/src/dorkbox/network/pipeline/rmi/LocalRmiEncoder.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2010 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.pipeline.rmi; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; - -import dorkbox.network.connection.ConnectionImpl; -import dorkbox.network.connection.EndPointBase; -import dorkbox.network.rmi.Rmi; -import dorkbox.util.FastThreadLocal; -import io.netty.channel.ChannelHandler.Sharable; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; - -/** - * This is for a LOCAL connection (same-JVM) - */ -@Sharable -public -class LocalRmiEncoder extends MessageToMessageEncoder { - - private static final Map, Boolean> transformObjectCache = new ConcurrentHashMap, Boolean>(EndPointBase.DEFAULT_THREAD_POOL_SIZE); - private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE(); - - private final FastThreadLocal> objectThreadLocals = new FastThreadLocal>() { - @Override - public - Map initialValue() { - return new WeakHashMap(8); - } - }; - - - public - LocalRmiEncoder() { - super(); - } - - @Override - protected - void encode(final ChannelHandlerContext context, final Object msg, final List out) throws Exception { - // have to change the rmi objects to proxy objects, with the server-assigned ID - // HOWEVER -- we cannot use the connection here (otherwise the logic is backwards). The connection must be set in other side - - - // we should check to see if this class is registered as having RMI methods present. - // if YES, then we have to send the corresponding RMI proxy object INSTEAD of the actual object. - // normally, it's OK to send the actual object. - // - // We specifically DO NOT do full serialization because it's not necessary --- we are running inside the same JVM. - final Class implClass = msg.getClass(); - Boolean needsTransform = transformObjectCache.get(implClass); - - if (needsTransform == null) { - boolean hasRmi = implClass.getAnnotation(Rmi.class) != null; - - if (hasRmi) { - // replace object - ConnectionImpl connection = (ConnectionImpl) context.pipeline() - .last(); - out.add(replaceFieldObjects(connection, msg, implClass)); - } - else { - transformObjectCache.put(implClass, Boolean.FALSE); - out.add(msg); - } - } - else if (needsTransform) { - ConnectionImpl connection = (ConnectionImpl) context.pipeline() - .last(); - // replace object - out.add(replaceFieldObjects(connection, msg, implClass)); - } else { - out.add(msg); - } - } - - private - Object replaceFieldObjects(final ConnectionImpl connection, final Object object, final Class implClass) { - Field[] rmiFields = fieldCache.get(implClass); - int length = rmiFields.length; - - Object rmiObject = null; - int[] rmiFieldIds = new int[length]; - for (int i = 0; i < length; i++) { - Field field = rmiFields[i]; - - // if it's an RMI object we want to write out a proxy object in the field instead of the actual object - try { - rmiObject = field.get(object); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - if (rmiObject == null) { - rmiFieldIds[i] = 0; // 0 means it was null - } - - final Map localWeakCache = objectThreadLocals.get(); - - Integer id = localWeakCache.get(rmiObject); - if (id == null) { - // duplicates are fine, as they represent the same object (as specified by the ID) on the remote side. - int registeredId = connection.getRegisteredId(rmiObject); - rmiFieldIds[i] = registeredId; - localWeakCache.put(rmiObject, registeredId); - } else { - rmiFieldIds[i] = id; - } - } - - LocalRmiClassEncoder localRmiClassEncoder = new LocalRmiClassEncoder(); - localRmiClassEncoder.rmiObject = object; - localRmiClassEncoder.rmiFieldIds = rmiFieldIds; - - return localRmiClassEncoder; - } -} diff --git a/src/dorkbox/network/pipeline/rmi/RmiFieldCache.java b/src/dorkbox/network/pipeline/rmi/RmiFieldCache.java deleted file mode 100644 index cbd72d78..00000000 --- a/src/dorkbox/network/pipeline/rmi/RmiFieldCache.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2016 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.network.pipeline.rmi; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - -import com.esotericsoftware.kryo.util.IdentityMap; - -import dorkbox.network.rmi.Rmi; - -/** - * Uses the "single writer principle" for fast access, but disregards 'single writer', because duplicates are OK - * - * This is for a LOCAL connection (same-JVM) - * - */ -class RmiFieldCache { - private volatile IdentityMap, Field[]> fieldCache = new IdentityMap, Field[]>(); - - private static final AtomicReferenceFieldUpdater rmiFieldsREF = - AtomicReferenceFieldUpdater.newUpdater(RmiFieldCache.class, - IdentityMap.class, - "fieldCache"); - - private static final RmiFieldCache INSTANCE = new RmiFieldCache(); - public static synchronized RmiFieldCache INSTANCE() { - return INSTANCE; - } - - private - RmiFieldCache() { - } - - Field[] get(final Class clazz) { - // duplicates are OK, because they will contain the same information, so we DO NOT care about single writers - - //noinspection unchecked - final IdentityMap, Field[]> identityMap = rmiFieldsREF.get(this); - - - Field[] rmiFields = identityMap.get(clazz); - if (rmiFields != null) { - return rmiFields; - } - - final ArrayList fields = new ArrayList(); - - for (Field field : clazz.getDeclaredFields()) { - if (field.getAnnotation(Rmi.class) != null) { - fields.add(field); - } - } - - - rmiFields = new Field[fields.size()]; - fields.toArray(rmiFields); - - // save in cache - fieldCache.put(clazz, rmiFields); - return rmiFields; - } -} diff --git a/src/dorkbox/network/rmi/Rmi.java b/src/dorkbox/network/rmi/Rmi.java index c4068fe5..5de2be5f 100644 --- a/src/dorkbox/network/rmi/Rmi.java +++ b/src/dorkbox/network/rmi/Rmi.java @@ -22,8 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This specifies to the serializer, that this class contains an RMI object, and that a specific field is an RMI object. Both are - * necessary. + * This specifies to the RMI system that a specific field in this RMI object is ALSO an RMI object */ @Retention(value = RetentionPolicy.RUNTIME) @Inherited diff --git a/src/dorkbox/network/rmi/RmiBridge.java b/src/dorkbox/network/rmi/RmiBridge.java index 09bdbd7e..7adbbefd 100644 --- a/src/dorkbox/network/rmi/RmiBridge.java +++ b/src/dorkbox/network/rmi/RmiBridge.java @@ -60,7 +60,7 @@ import dorkbox.util.collections.ObjectIntMap; *

*

* Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link - * Connection#getRemoteObject(Class, RemoteObjectCallback)} for the registered objects. + * Connection#createRemoteObject(Class, RemoteObjectCallback)} for the registered objects. *

* It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a return value which * is not {@link RemoteObject#setAsync(boolean) ignored}, an extra byte is written. If the type of a parameter is not final (note that @@ -414,7 +414,7 @@ class RmiBridge { } /** - * Warning. This is an advanced method. You should probably be using {@link Connection#getRemoteObject(Class, RemoteObjectCallback)} + * Warning. This is an advanced method. You should probably be using {@link Connection#createRemoteObject(Class, RemoteObjectCallback)} *

*

* Returns a proxy object that implements the specified interfaces. Methods invoked on the proxy object will be invoked remotely on the @@ -432,25 +432,29 @@ class RmiBridge { * have the proxy object replaced with the registered object. * * @see RemoteObject + * + * @param connection this is really the network client -- there is ONLY ever 1 connection + * @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID + * @param iFace this is the RMI interface */ public - RemoteObject createProxyObject(Connection connection, int objectID, Class iface) { + RemoteObject createProxyObject(Connection connection, int objectID, Class iFace) { if (connection == null) { throw new IllegalArgumentException("connection cannot be null."); } - if (iface == null) { + if (iFace == null) { throw new IllegalArgumentException("iface cannot be null."); } - if (!iface.isInterface()) { + if (!iFace.isInterface()) { throw new IllegalArgumentException("iface must be an interface."); } Class[] temp = new Class[2]; temp[0] = RemoteObject.class; - temp[1] = iface; + temp[1] = iFace; return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(), temp, - new RmiProxyHandler(connection, objectID, iface)); + new RmiProxyHandler(connection, objectID, iFace)); } } diff --git a/src/dorkbox/network/rmi/RmiProxyHandler.java b/src/dorkbox/network/rmi/RmiProxyHandler.java index 1c836579..fa7afdca 100644 --- a/src/dorkbox/network/rmi/RmiProxyHandler.java +++ b/src/dorkbox/network/rmi/RmiProxyHandler.java @@ -56,6 +56,7 @@ import dorkbox.network.serialization.RmiSerializationManager; *

* The only methods than can be invoked are INTERFACE methods and OBJECT methods */ +public class RmiProxyHandler implements InvocationHandler { private final Logger logger; @@ -66,7 +67,6 @@ class RmiProxyHandler implements InvocationHandler { private final boolean[] pendingResponses = new boolean[64]; private final Connection connection; - private final Class iFace; public final int objectID; // this is the RMI id public final int ID; // this is the KRYO id @@ -96,7 +96,6 @@ class RmiProxyHandler implements InvocationHandler { super(); this.connection = connection; - this.iFace = iFace; this.objectID = objectID; this.proxyString = ""; diff --git a/src/dorkbox/network/rmi/RmiRegistration.java b/src/dorkbox/network/rmi/RmiRegistration.java index 31df50f3..079fe3f7 100644 --- a/src/dorkbox/network/rmi/RmiRegistration.java +++ b/src/dorkbox/network/rmi/RmiRegistration.java @@ -20,13 +20,27 @@ package dorkbox.network.rmi; */ public class RmiRegistration { + public boolean isRequest; + + /** + * this is null if there are problems creating an object on the remote side, otherwise it is non-null. + */ public Object remoteObject; + + /** + * this is used to create a NEW rmi object on the REMOTE side (these are bound the to connection. They are NOT GLOBAL, ie: available on all connections) + */ public Class interfaceClass; - // this is used to get specific, GLOBAL rmi objects (objects that are not bound to a single connection) - public int remoteObjectId; + /** + * this is used to get specific, GLOBAL rmi objects (objects that are not bound to a single connection) + */ + public int rmiId; - public int rmiID; + /** + * this is the callback ID assigned by the LOCAL side, to know WHICH RMI callback to call when we have a remote object available + */ + public int callbackId; @SuppressWarnings("unused") private @@ -34,35 +48,35 @@ class RmiRegistration { // for serialization } - // When requesting a new remote object to be created. - // SENT FROM "client" -> "server" + /** + * When requesting a new or existing remote object + * SENT FROM "local" -> "remote" + * + * @param interfaceClass the class to create + * @param rmiId the RMI id to get from the REMOTE side + * @param callbackId the rmi callback ID on the LOCAL side, to know which callback to use + */ public - RmiRegistration(final Class interfaceClass, final int rmiID) { + RmiRegistration(final Class interfaceClass, final int rmiId, final int callbackId) { + isRequest = true; this.interfaceClass = interfaceClass; - this.rmiID = rmiID; - } - - // When requesting a new remote object to be created. - // SENT FROM "client" -> "server" - public - RmiRegistration(final int remoteObjectId, final int rmiID) { - this.remoteObjectId = remoteObjectId; - this.rmiID = rmiID; + this.rmiId = rmiId; + this.callbackId = callbackId; } - // When there was an error creating the remote object. - // SENT FROM "server" -> "client" + /** + * This is when we successfully created a new object (if there was an error, remoteObject is null) + * SENT FROM "remote" -> "local" + * + * @param callbackId the rmi callback ID on the LOCAL side, to know which callback to use + */ public - RmiRegistration(final int rmiID) { - this.rmiID = rmiID; - } - - // This is when we successfully created a new object - // SENT FROM "server" -> "client" - public - RmiRegistration(final Object remoteObject, final int rmiID) { + RmiRegistration(final Class interfaceClass, final int rmiId, final int callbackId, final Object remoteObject) { + isRequest = false; + this.interfaceClass = interfaceClass; + this.rmiId = rmiId; + this.callbackId = callbackId; this.remoteObject = remoteObject; - this.rmiID = rmiID; } } diff --git a/src/dorkbox/network/rmi/TimeoutException.java b/src/dorkbox/network/rmi/TimeoutException.java index 3cc16936..5c9054f8 100644 --- a/src/dorkbox/network/rmi/TimeoutException.java +++ b/src/dorkbox/network/rmi/TimeoutException.java @@ -42,7 +42,7 @@ import java.io.IOException; * * @author Nathan Sweet * @see dorkbox.network.connection.Connection#getRemoteObject(int, RemoteObjectCallback) - * @see dorkbox.network.connection.Connection#getRemoteObject(Class, RemoteObjectCallback) + * @see dorkbox.network.connection.Connection#createRemoteObject(Class, RemoteObjectCallback) */ public class TimeoutException extends IOException { diff --git a/src/dorkbox/network/serialization/Serialization.java b/src/dorkbox/network/serialization/Serialization.java index c4d2c8dd..f754e053 100644 --- a/src/dorkbox/network/serialization/Serialization.java +++ b/src/dorkbox/network/serialization/Serialization.java @@ -261,8 +261,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag private RemoteObjectSerializer remoteObjectSerializer; // used to track which interface -> implementation, for use by RMI - private final IntMap> rmiIdToImpl = new IntMap>(); - private final IntMap> rmiIdToIface = new IntMap>(); + private final IntMap> rmiKryoIdToImpl = new IntMap>(); + private final IntMap> rmiKryoIdToIface = new IntMap>(); private final IdentityMap, Class> rmiIfaceToImpl = new IdentityMap, Class>(); private final IdentityMap, Class> rmiImplToIface = new IdentityMap, Class>(); @@ -380,8 +380,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag // sets up the RMI, so when we receive the iface class from the client, we know what impl to use // if this is over-written, we don't care. - rmiIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read - rmiIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write + rmiKryoIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read + rmiKryoIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write } } @@ -575,8 +575,8 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag */ @Override public - Class getRmiIface(Class implementation) { - return rmiImplToIface.get(implementation); + Class getRmiImpl(Class iface) { + return rmiIfaceToImpl.get(iface); } /** @@ -804,6 +804,12 @@ class Serialization implements CryptoSerializationManager, RmiSerializationManag return methodCache.get(classId); } + @Override + public + boolean isRmiEnabled() { + return usesRmi; + } + @Override public synchronized boolean initialized() {