diff --git a/src/dorkbox/network/serialization/ClassRegistration.kt b/src/dorkbox/network/serialization/ClassRegistration.kt index d7c307da..d3f225d5 100644 --- a/src/dorkbox/network/serialization/ClassRegistration.kt +++ b/src/dorkbox/network/serialization/ClassRegistration.kt @@ -63,7 +63,7 @@ internal abstract class ClassRegistration(val clazz: Class<*>, val serializer: S // otherwise, we are OK to continue to register this register(kryo) - if (serializer != null && overriddenSerializer != serializer) { + if (serializer != null && overriddenSerializer != null && overriddenSerializer != serializer) { info = "$info (Replaced $overriddenSerializer)" } diff --git a/src/dorkbox/network/serialization/ClassRegistrationForRmi.kt b/src/dorkbox/network/serialization/ClassRegistrationForRmi.kt index b6d503ab..15261523 100644 --- a/src/dorkbox/network/serialization/ClassRegistrationForRmi.kt +++ b/src/dorkbox/network/serialization/ClassRegistrationForRmi.kt @@ -45,7 +45,7 @@ import dorkbox.network.rmi.messages.RmiServerSerializer * If the impl object 'lives' on the SERVER, then the server must tell the client about the iface ID */ internal class ClassRegistrationForRmi(ifaceClass: Class<*>, - val implClass: Class<*>?, + var implClass: Class<*>?, serializer: RmiServerSerializer) : ClassRegistration(ifaceClass, serializer) { /** * In general: @@ -106,21 +106,55 @@ internal class ClassRegistrationForRmi(ifaceClass: Class<*>, override fun register(kryo: KryoExtra, rmi: RmiHolder) { // we override this, because we ALWAYS will call our RMI registration! - // have to get the ID for the interface (if it exists) - val registration = kryo.classResolver.getRegistration(clazz) // this is ifaceClass, and must match what is defined on the rmi client - if (registration != null) { - id = registration.id + if (implClass == null) { + // this means we are the RMI-CLIENT - // override that registration - kryo.register(implClass, serializer, id) - } else { - // now register the impl class - id = kryo.register(implClass, serializer).id + // see if we have the IMPL registered? NOT LIKELY, but possible + val existingId = rmi.ifaceToId[clazz] + if (existingId != null) { + implClass = rmi.idToImpl[existingId] + } + + if (implClass == null) { + // then we just register the interface class! + + val registration = kryo.classResolver.getRegistration(clazz) + if (registration != null) { + id = registration.id + + // override that registration + kryo.register(clazz, serializer, id) + } + else { + // just register the iface class + id = kryo.register(clazz, serializer).id + } + } } + + + if (implClass != null) { + // this means we are the RMI-SERVER + val registration = kryo.classResolver.getRegistration(implClass) // we cannot register iface classes! + if (registration != null) { + id = registration.id + + // override that registration + kryo.register(implClass, serializer, id) + } + else { + // now register the impl class + id = kryo.register(implClass, serializer).id + } + } + + + + // have to get the ID for the interface (if it exists) info = if (implClass == null) { "Registered $id -> (RMI-CLIENT) ${clazz.name}" } else { - "Registered $id -> (RMI-SERVER) ${clazz.name} -> ${implClass.name}" + "Registered $id -> (RMI-SERVER) ${clazz.name} -> ${implClass!!.name}" } // now, we want to save the relationship between classes and kryoId @@ -128,8 +162,10 @@ internal class ClassRegistrationForRmi(ifaceClass: Class<*>, rmi.idToIface[id] = clazz // we have to know what the IMPL class is so we can create it for a "createObject" RMI command - rmi.implToId[implClass] = id - rmi.idToImpl[id] = implClass + if (implClass != null) { + rmi.implToId[implClass] = id + rmi.idToImpl[id] = implClass + } } override fun getInfoArray(): Array { @@ -137,7 +173,7 @@ internal class ClassRegistrationForRmi(ifaceClass: Class<*>, return if (implClass == null) { arrayOf(4, id, clazz.name, serializer!!::class.java.name, "") } else { - arrayOf(4, id, clazz.name, serializer!!::class.java.name, implClass.name) + arrayOf(4, id, clazz.name, serializer!!::class.java.name, implClass!!.name) } } } diff --git a/src/dorkbox/network/serialization/Serialization.kt b/src/dorkbox/network/serialization/Serialization.kt index 43fd31f8..05f8e6da 100644 --- a/src/dorkbox/network/serialization/Serialization.kt +++ b/src/dorkbox/network/serialization/Serialization.kt @@ -228,14 +228,22 @@ open class Serialization(private val references: Boolean = true, private val fac * * This is NOT bi-directional, and this endpoint cannot access or create remote objects on the "remote client". * + * @param ifaceClass this must be the interface class used for RMI + * @param implClass this must be the implementation class used for RMI + * If *null* it means that this endpoint is the rmi-client + * If *not-null* it means that this endpoint is the rmi-server + * * @throws IllegalArgumentException if the iface/impl have previously been overridden */ @Synchronized - fun registerRmi(ifaceClass: Class, implClass: Class): Serialization { + fun registerRmi(ifaceClass: Class, implClass: Class? = null): Serialization { require(!initialized.value) { "Serialization 'registerRmi(Class, Class)' cannot happen after client/server initialization!" } require(ifaceClass.isInterface) { "Cannot register an implementation for RMI access. It must be an interface." } - require(!implClass.isInterface) { "Cannot register an interface for RMI implementations. It must be an implementation." } + + if (implClass != null) { + require(!implClass.isInterface) { "Cannot register an interface for RMI implementations. It must be an implementation." } + } classesToRegister.add(ClassRegistrationForRmi(ifaceClass, implClass, rmiServerSerializer)) return this @@ -332,7 +340,6 @@ open class Serialization(private val references: Boolean = true, private val fac return true } - require(classesToRegister.isEmpty()) { "Unable to initialize a non-empty class registration state! Make sure there are no serialization registrations for the client!" } initializeClient(kryoRegistrationDetailsFromServer) } } @@ -459,7 +466,18 @@ open class Serialization(private val references: Boolean = true, private val fac @Suppress("UNCHECKED_CAST") private fun initializeClient(kryoRegistrationDetailsFromServer: ByteArray): Boolean { - val kryo = initKryo() + // we have to allow CUSTOM classes to register (where the order does not matter), so that if the CLIENT is the RMI-SERVER, it can + // specify IMPL classes for RMI. + classesToRegister.forEach { registration -> + require(registration is ClassRegistrationForRmi) { "Unable to initialize a class registrations for anything OTHER than RMI!! To fix this, remove ${registration.clazz}" } + } + val classesToRegisterForRmi = listOf(*classesToRegister.toTypedArray()) as List + classesToRegister.clear() + + + + + var kryo = initKryo() val input = AeronInput(kryoRegistrationDetailsFromServer) val clientClassRegistrations = kryo.readCompressed(logger, input, kryoRegistrationDetailsFromServer.size) as Array> @@ -486,16 +504,18 @@ open class Serialization(private val references: Boolean = true, private val fac // we literally want everything to be 100% the same. // the only WEIRD case is when the client == rmi-server (in which case, the IMPL object is on the client) // for this, the server (rmi-client) WILL ALSO have the same registration info. (bi-directional RMI, but not really) - val implClazzName = bytes[4] as String - var implClass: Class<*>? = null + val implClassName = bytes[4] as String + var implClass: Class<*>? = classesToRegisterForRmi.firstOrNull { it.clazz.name == clazzName }?.implClass - if (implClazzName.isNotEmpty()) { + // if we do not have the impl class specified by the registrations for RMI, then do a lookup to see if we have access to it as the client + if (implClass == null) { try { - implClass = Class.forName(implClazzName) + implClass = Class.forName(implClassName) } catch (ignored: Exception) { } } + // implClass MIGHT BE NULL! classesToRegister.add(ClassRegistrationForRmi(clazz, implClass, rmiServerSerializer)) } @@ -512,11 +532,8 @@ open class Serialization(private val references: Boolean = true, private val fac // we have to re-init so the registrations are set! initKryo() - // now do a round-trip through the server serialization to make sure our byte arrays are THE SAME. - initializeClassRegistrations() - - // verify the registration ID data is THE SAME! - return savedRegistrationDetails.contentEquals(kryoRegistrationDetailsFromServer) + // now do a round-trip through the class registrations + return initializeClassRegistrations() } /**