From 94e835218a43eda92b494408c8fb91e1f972cde5 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 12 Aug 2020 23:30:43 +0200 Subject: [PATCH] Added RMI save/get/create methods to client/server --- src/dorkbox/network/Client.kt | 277 +++++++++++++++++++++++++++------- src/dorkbox/network/Server.kt | 73 +++++++++ 2 files changed, 295 insertions(+), 55 deletions(-) diff --git a/src/dorkbox/network/Client.kt b/src/dorkbox/network/Client.kt index f7d85928..6d1050d7 100644 --- a/src/dorkbox/network/Client.kt +++ b/src/dorkbox/network/Client.kt @@ -350,64 +350,22 @@ open class Client(config: Configuration = Configuration // return connection!!.idAsHex() // } + + + + + + /** - * Tells the remote connection to create a new global object that implements the specified interface. - * - * The methods on the returned object will remotely execute on the (remotely) created object - * The callback will be notified when the remote object has been created. - * - * If you want to create a connection specific remote object, call [Connection.create(Int, RemoteObjectCallback)] on a connection - * The callback will be notified when the remote object has been created. - * - * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side - * will have the proxy object replaced with the registered (non-proxy) object. - * - * - * If one wishes to change the remote object behavior, cast the object [RemoteObject] to access the different methods, for example: - * ie: `val remoteObject = test as RemoteObject` - * - * @see RemoteObject + * Fetches the connection used by the client, this is only valid after the client has connected */ - suspend inline fun createObject(noinline callback: suspend (Iface) -> Unit) { - val classId = serialization.getClassId(Iface::class.java) - rmiSupport.createGlobalRemoteObject(getConnection(), classId, callback) + fun getConnection(): CONNECTION { + return connection as CONNECTION } /** - * Gets a global remote object via the ID. - * - * Global remote objects are accessible to ALL connections, where as a connection specific remote object is only accessible/visible - * to the connection. - * - * If you want to access a connection specific remote object, call [Connection.get(Int, RemoteObjectCallback)] on a connection - * The callback will be notified when the remote object has been created. - * - * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side - * will have the proxy object replaced with the registered (non-proxy) object. - * - * If one wishes to change the remote object behavior, cast the object to a [RemoteObject] to access the different methods, for example: - * ie: `val remoteObject = test as RemoteObject` - * - * @see RemoteObject + * @throws ClientException when a message cannot be sent */ - fun getObject(objectId: Int, interfaceClass: Class): Iface { - return rmiSupport.getGlobalRemoteObject(getConnection(), this, objectId, interfaceClass) - } - - /** - * Fetches the connection used by the client. - * - * - * Make **sure** that you only call this **after** the client connects! - * - * - * This is preferred to [EndPoint.getConnections], as it properly does some error checking - */ - fun getConnection(): C { - return connection as C - } - - @Throws(ClientException::class) suspend fun send(message: Any) { val c = connection if (c != null) { @@ -417,7 +375,9 @@ open class Client(config: Configuration = Configuration } } - @Throws(ClientException::class) + /** + * @throws ClientException when a message cannot be sent + */ suspend fun send(message: Any, priority: Byte) { val c = connection if (c != null) { @@ -427,7 +387,9 @@ open class Client(config: Configuration = Configuration } } - @Throws(ClientException::class) + /** + * @throws ClientException when a ping cannot be sent + */ suspend fun ping(): Ping { val c = connection if (c != null) { @@ -443,7 +405,7 @@ open class Client(config: Configuration = Configuration if (savedPublicKey != null) { val logger2 = logger if (logger2.isDebugEnabled) { - logger2.debug("Deleting remote IP address key ${NetworkUtil.IP.toString(hostAddress)}") + logger2.debug("Deleting remote IP address key ${IPv4.toString(hostAddress)}") } settingsStore.removeRegisteredServerKey(hostAddress) } @@ -534,4 +496,209 @@ open class Client(config: Configuration = Configuration // // make sure we're not waiting on registration //// stopRegistration() // } + + override fun close() { + val con = connection + connection = null + if (con != null) { + handshake.removeConnection(con) + } + + super.close() + } + + + // RMI notes (in multiple places, copypasta, because this is confusing if not written down + // + // only server can create a global object (in itself, via save) + // server + // -> saveGlobal (global) + // + // client + // -> save (connection) + // -> get (connection) + // -> create (connection) + // -> saveGlobal (global) + // -> getGlobal (global) + // + // connection + // -> save (connection) + // -> get (connection) + // -> getGlobal (global) + // -> create (connection) + + + // + // + // RMI - connection + // + // + + /** + * Tells us to save an an already created object in the CONNECTION scope, so a remote connection can get it via [Connection.getObject] + * + * - This object is NOT THREAD SAFE, and is meant to ONLY be used from a single thread! + * + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * + * @return the newly registered RMI ID for this object. [RemoteObjectStorage.INVALID_RMI] means it was invalid (an error log will be emitted) + * + * @see RemoteObject + */ + fun saveObject(`object`: Any): Int { + return rmiConnectionSupport.saveImplObject(`object`) + } + + /** + * Tells us to save an an already created object in the CONNECTION scope using the specified ID, so a remote connection can get it via [Connection.getObject] + * + * + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * @return true if the object was successfully saved for the specified ID. If false, an error log will be emitted + * + * @see RemoteObject + */ + fun saveObject(`object`: Any, objectId: Int): Boolean { + return rmiConnectionSupport.saveImplObject(`object`, objectId) + } + + /** + * Get a CONNECTION scope REMOTE object via the ID. + * + * Global remote objects are accessible to ALL connections, where as a connection specific remote object is only accessible/visible + * to the connection. + * + * If you want to access a connection specific remote object, call [Connection.get(Int, RemoteObjectCallback)] on a connection + * The callback will be notified when the remote object has been created. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the remote object behavior, cast the object to a [RemoteObject] to access the different methods, for example: + * ie: `val remoteObject = test as RemoteObject` + * + * @see RemoteObject + */ + inline fun getObject(objectId: Int): Iface { + // NOTE: It's not possible to have reified inside a virtual function + // https://stackoverflow.com/questions/60037849/kotlin-reified-generic-in-virtual-function + @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") + return rmiConnectionSupport.getRemoteObject(getConnection(), this, objectId, Iface::class.java) + } + + /** + * Tells the remote connection to create a new proxy object that implements the specified interface in the CONNECTION scope. + * + * The methods on this object "map" to an object that is created remotely. + * + * The callback will be notified when the remote object has been created. + * + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * @see RemoteObject + */ + suspend inline fun createObject(noinline callback: suspend (Iface) -> Unit) { + // NOTE: It's not possible to have reified inside a virtual function + // https://stackoverflow.com/questions/60037849/kotlin-reified-generic-in-virtual-function + val classId = serialization.getClassId(Iface::class.java) + + @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") + rmiConnectionSupport.createRemoteObject(getConnection(), classId, callback) + } + + // + // + // RMI - global + // + // + + /** + * Tells us to save an an already created object in the GLOBAL scope, so a remote connection can get it via [Connection.getGlobalObject] + * + * + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * + * @return the newly registered RMI ID for this object. [RemoteObjectStorage.INVALID_RMI] means it was invalid (an error log will be emitted) + * + * @see RemoteObject + */ + fun saveGlobalObject(`object`: Any): Int { + return rmiGlobalSupport.saveImplObject(`object`) + } + + /** + * Tells us to save an an already created object in the GLOBAL scope using the specified ID, so a remote connection can get it via [Connection.getGlobalObject] + * + * + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * @return true if the object was successfully saved for the specified ID. If false, an error log will be emitted + * + * @see RemoteObject + */ + fun saveGlobalObject(`object`: Any, objectId: Int): Boolean { + return rmiGlobalSupport.saveImplObject(`object`, objectId) + } + + /** + * Get a GLOBAL scope remote object via the ID. + * + * Global remote objects are accessible to ALL connections, where as a connection specific remote object is only accessible/visible + * to the connection. + * + * If you want to access a connection specific remote object, call [Connection.get(Int, RemoteObjectCallback)] on a connection + * The callback will be notified when the remote object has been created. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the remote object behavior, cast the object to a [RemoteObject] to access the different methods, for example: + * ie: `val remoteObject = test as RemoteObject` + * + * @see RemoteObject + */ + inline fun getGlobalObject(objectId: Int): Iface { + // NOTE: It's not possible to have reified inside a virtual function + // https://stackoverflow.com/questions/60037849/kotlin-reified-generic-in-virtual-function + @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") + return rmiGlobalSupport.getGlobalRemoteObject(getConnection(), this, objectId, Iface::class.java) + } } diff --git a/src/dorkbox/network/Server.kt b/src/dorkbox/network/Server.kt index 9adc58c0..c5de2c02 100644 --- a/src/dorkbox/network/Server.kt +++ b/src/dorkbox/network/Server.kt @@ -464,4 +464,77 @@ open class Server(config: ServerConfiguration = ServerC // } // return STATE.CONTINUE // } + + + + // RMI notes (in multiple places, copypasta, because this is confusing if not written down + // + // only server can create a global object (in itself, via save) + // server + // -> saveGlobal (global) + // + // client + // -> save (connection) + // -> get (connection) + // -> create (connection) + // -> saveGlobal (global) + // -> getGlobal (global) + // + // connection + // -> save (connection) + // -> get (connection) + // -> getGlobal (global) + // -> create (connection) + + + // + // + // RMI + // + // + + + + /** + * Tells us to save an an already created object, GLOBALLY, so a remote connection can get it via [Connection.getObject] + * + * FOR REMOTE CONNECTIONS: + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * + * @return the newly registered RMI ID for this object. [RemoteObjectStorage.INVALID_RMI] means it was invalid (an error log will be emitted) + * + * @see RemoteObject + */ + fun saveGlobalObject(`object`: Any): Int { + return rmiGlobalSupport.saveImplObject(logger, `object`) + } + + /** + * Tells us to save an an already created object, GLOBALLY using the specified ID, so a remote connection can get it via [Connection.getObject] + * + * FOR REMOTE CONNECTIONS: + * Methods that return a value will throw [TimeoutException] if the response is not received with the + * response timeout [RemoteObject.responseTimeout]. + * + * If a proxy returned from this method is part of an object graph sent over the network, the object graph on the receiving side + * will have the proxy object replaced with the registered (non-proxy) object. + * + * If one wishes to change the default behavior, cast the object to access the different methods. + * ie: `val remoteObject = test as RemoteObject` + * + * @return true if the object was successfully saved for the specified ID. If false, an error log will be emitted + * + * @see RemoteObject + */ + fun saveGlobalObject(`object`: Any, objectId: Int): Boolean { + return rmiGlobalSupport.saveImplObject(logger, `object`, objectId) + } }