Merge branch 'fix_RMI'
# Conflicts: # src/dorkbox/network/connection/CryptoSerializationManager.java
This commit is contained in:
commit
9bb03fa919
@ -31,6 +31,7 @@ import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerC
|
||||
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerClientUDP;
|
||||
import dorkbox.network.connection.registration.remote.RegistrationRemoteHandlerClientUDT;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.TimeoutException;
|
||||
import dorkbox.network.util.udt.UdtEndpointProxy;
|
||||
import dorkbox.util.NamedThreadFactory;
|
||||
@ -329,10 +330,10 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
||||
}
|
||||
}
|
||||
|
||||
connection = this.connectionManager.getConnection0();
|
||||
// RMI methods are usually created during the connection phase. We should wait until they are finished
|
||||
waitForRmi(connectionTimeout);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasRemoteKeyChanged() {
|
||||
@ -423,11 +424,12 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new proxy object that implements the specified interface. Methods invoked on the proxy object will be
|
||||
* invoked remotely on the object with the specified ID in the ObjectSpace for the current connection.
|
||||
* <p/>
|
||||
* This will request a registration ID from the remote endpoint, <b>and will block</b> until the object has been returned.
|
||||
* <p/>
|
||||
* Tells the remote connection to create a new proxy object that implements the specified interface. The methods on this object "map"
|
||||
* to an object that is created remotely.
|
||||
* <p>
|
||||
* The callback will be notified when the remote object has been created.
|
||||
* <p>
|
||||
* <p>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the
|
||||
* {@link RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
* <p/>
|
||||
@ -437,39 +439,44 @@ class Client<C extends Connection> extends EndPointClient<C> implements Connecti
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p>
|
||||
* If one wishes to change the default behavior, cast the object to access the different methods.
|
||||
* ie: `RemoteObject remoteObject = (RemoteObject) test;`
|
||||
*
|
||||
* @see RemoteObject
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
<Iface> Iface getRemoteObject(final Class<Iface> interfaceClass) throws IOException {
|
||||
return this.connectionManager.getConnection0()
|
||||
.getRemoteObject(interfaceClass);
|
||||
<Iface> void getRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) throws IOException {
|
||||
this.connectionManager.getConnection0().getRemoteObject(interfaceClass, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new proxy object implements the specified interface. Methods invoked on the proxy object will be invoked remotely on the
|
||||
* object with the specified ID in the ObjectSpace for the current connection.
|
||||
* Tells the remote connection to create a new proxy object that implements the specified interface. The methods on this object "map"
|
||||
* to an object that is created remotely.
|
||||
* <p>
|
||||
* The callback will be notified when the remote object has been created.
|
||||
* <p>
|
||||
* <p>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the
|
||||
* {@link RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
* <p/>
|
||||
* This will REUSE a registration ID from the remote endpoint, <b>and will block</b> until the object has been returned.
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must
|
||||
* not be called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a
|
||||
* void return value can be called on the update thread.
|
||||
* <p/>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the {@link
|
||||
* RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
* <p/>
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must not be
|
||||
* called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a void return value can be
|
||||
* called on the update thread.
|
||||
* <p/>
|
||||
* 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 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.
|
||||
* <p>
|
||||
* If one wishes to change the default behavior, cast the object to access the different methods.
|
||||
* ie: `RemoteObject remoteObject = (RemoteObject) test;`
|
||||
*
|
||||
* @see RemoteObject
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
<Iface> Iface getRemoteObject(final int objectId) throws IOException {
|
||||
return this.connectionManager.getConnection0()
|
||||
.getRemoteObject(objectId);
|
||||
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) throws IOException {
|
||||
this.connectionManager.getConnection0().getRemoteObject(objectId, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,15 +64,6 @@ class Configuration {
|
||||
*/
|
||||
public CryptoSerializationManager serialization = null;
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. 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. If the
|
||||
* type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
*/
|
||||
public boolean rmiEnabled = false;
|
||||
|
||||
/**
|
||||
* Sets the executor used to invoke methods when an invocation is received from a remote endpoint. By default, no executor is set and
|
||||
* invocations occur on the network thread, which should not be blocked for long.
|
||||
|
@ -15,14 +15,15 @@
|
||||
*/
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.connection.idle.IdleSender;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.TimeoutException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public
|
||||
interface Connection {
|
||||
@ -95,59 +96,55 @@ interface Connection {
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Marks the connection to be closed as soon as possible. This is evaluated when the current
|
||||
* thread execution returns to the network stack.
|
||||
* Marks the connection to be closed as soon as possible. This is evaluated when the current thread execution returns to the network stack.
|
||||
*/
|
||||
void closeAsap();
|
||||
|
||||
/**
|
||||
* Returns a new proxy object implements the specified interface. Methods invoked on the proxy object will be
|
||||
* invoked remotely on the object with the specified ID in the ObjectSpace for the current connection.
|
||||
* <p/>
|
||||
* This will request a registration ID from the remote endpoint, <b>and will block</b> until the object
|
||||
* has been returned.
|
||||
* <p/>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the
|
||||
* response is not received with the
|
||||
* Tells the remote connection to create a new proxy object that implements the specified interface. The methods on this object "map"
|
||||
* to an object that is created remotely.
|
||||
* <p>
|
||||
* The callback will be notified when the remote object has been created.
|
||||
* <p>
|
||||
* <p>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the
|
||||
* {@link RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
* <p/>
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false
|
||||
* (the default), then methods that return a value must not be called from
|
||||
* the update thread for the connection. An exception will be thrown if this
|
||||
* occurs. Methods with a void return value can be called on the update
|
||||
* thread.
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must
|
||||
* not be called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a
|
||||
* void return value can be called on the update thread.
|
||||
* <p/>
|
||||
* 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 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: `RemoteObject remoteObject = (RemoteObject) test;`
|
||||
*
|
||||
* @see RemoteObject
|
||||
*/
|
||||
<Iface, Impl extends Iface> Iface createProxyObject(final Class<Impl> remoteImplementationClass) throws IOException;
|
||||
|
||||
<Iface> void getRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a new proxy object implements the specified interface. Methods invoked on the proxy object will be
|
||||
* invoked remotely on the object with the specified ID in the ObjectSpace for the current connection.
|
||||
* <p/>
|
||||
* This will REUSE a registration ID from the remote endpoint, <b>and will block</b> until the object
|
||||
* has been returned.
|
||||
* <p/>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the
|
||||
* response is not received with the
|
||||
* Tells the remote connection to create a new proxy object that implements the specified interface. The methods on this object "map"
|
||||
* to an object that is created remotely.
|
||||
* <p>
|
||||
* The callback will be notified when the remote object has been created.
|
||||
* <p>
|
||||
* <p>
|
||||
* Methods that return a value will throw {@link TimeoutException} if the response is not received with the
|
||||
* {@link RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
* <p/>
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false
|
||||
* (the default), then methods that return a value must not be called from
|
||||
* the update thread for the connection. An exception will be thrown if this
|
||||
* occurs. Methods with a void return value can be called on the update
|
||||
* thread.
|
||||
* If {@link RemoteObject#setAsync(boolean) non-blocking} is false (the default), then methods that return a value must
|
||||
* not be called from the update thread for the connection. An exception will be thrown if this occurs. Methods with a
|
||||
* void return value can be called on the update thread.
|
||||
* <p/>
|
||||
* 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 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.
|
||||
* <p>
|
||||
* If one wishes to change the default behavior, cast the object to access the different methods.
|
||||
* ie: `RemoteObject remoteObject = (RemoteObject) test;`
|
||||
*
|
||||
* @see RemoteObject
|
||||
*/
|
||||
<Iface, Impl extends Iface> Iface getProxyObject(final int objectId) throws IOException;
|
||||
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) throws IOException;
|
||||
}
|
||||
|
@ -15,6 +15,19 @@
|
||||
*/
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.connection.idle.IdleSender;
|
||||
@ -25,10 +38,13 @@ import dorkbox.network.connection.ping.PingTuple;
|
||||
import dorkbox.network.connection.wrapper.ChannelNetworkWrapper;
|
||||
import dorkbox.network.connection.wrapper.ChannelNull;
|
||||
import dorkbox.network.connection.wrapper.ChannelWrapper;
|
||||
import dorkbox.network.rmi.RMI;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.Rmi;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.network.rmi.RmiRegistration;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.collections.IntMap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@ -43,18 +59,6 @@ import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
||||
/**
|
||||
@ -79,7 +83,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
private final AtomicBoolean messageInProgress = new AtomicBoolean(false);
|
||||
|
||||
private ISessionManager<Connection> sessionManager;
|
||||
private ChannelWrapper channelWrapper;
|
||||
private ChannelWrapper<Connection> channelWrapper;
|
||||
private boolean isLoopback;
|
||||
|
||||
private volatile PingFuture pingFuture = null;
|
||||
@ -96,21 +100,23 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
// back to the network stack
|
||||
private boolean closeAsap = false;
|
||||
|
||||
private volatile ObjectRegistrationLatch objectRegistrationLatch;
|
||||
private final Object remoteObjectLock = new Object();
|
||||
private final RmiBridge rmiBridge;
|
||||
|
||||
private final Map<Integer, RemoteObject> proxyIdCache = Collections.synchronizedMap(new WeakHashMap<Integer, RemoteObject>(8));
|
||||
|
||||
// The IV for AES-GCM must be 12 bytes, since it's 4 (salt) + 8 (external counter) + 4 (GCM counter)
|
||||
// The 12 bytes IV is created during connection registration, and during the AES-GCM crypto, we override the last 8 with this
|
||||
// counter, which is also transmitted as an optimized int. (which is why it starts at 0, so the transmitted bytes are small)
|
||||
private final AtomicLong aes_gcm_iv = new AtomicLong(0);
|
||||
|
||||
//
|
||||
// RMI fields
|
||||
//
|
||||
private final RmiBridge rmiBridge;
|
||||
private final Map<Integer, RemoteObject> proxyIdCache = new WeakHashMap<Integer, RemoteObject>(8);
|
||||
private final IntMap<RemoteObjectCallback> rmiRegistrationCallbacks = new IntMap<>();
|
||||
private int rmiRegistrationID = 0; // protected by synchronized (rmiRegistrationCallbacks)
|
||||
|
||||
/**
|
||||
* All of the parameters can be null, when metaChannel wants to get the base class type
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public
|
||||
ConnectionImpl(final Logger logger, final EndPoint endPoint, final RmiBridge rmiBridge) {
|
||||
this.logger = logger;
|
||||
@ -123,6 +129,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
* <p/>
|
||||
* This happens BEFORE prep.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void init(final ChannelWrapper channelWrapper, final ConnectionManager<Connection> sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.channelWrapper = channelWrapper;
|
||||
@ -155,6 +162,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
* because multiple protocols can be performing crypto AT THE SAME TIME, and so we have to make sure that operations don't
|
||||
* clobber each other
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
ParametersWithIV getCryptoParameters() {
|
||||
return this.channelWrapper.cryptoParameters();
|
||||
@ -167,6 +175,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
* The 12 bytes IV is created during connection registration, and during the AES-GCM crypto, we override the last 8 with this
|
||||
* counter, which is also transmitted as an optimized int. (which is why it starts at 0, so the transmitted bytes are small)
|
||||
*/
|
||||
@Override
|
||||
public final
|
||||
long getNextGcmSequence() {
|
||||
return aes_gcm_iv.getAndIncrement();
|
||||
@ -950,102 +959,144 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
// RMI methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Internal call CLIENT ONLY.
|
||||
* <p>
|
||||
* RMI methods are usually created during the connection phase. We should wait until they are finished
|
||||
*/
|
||||
void waitForRmi(final int connectionTimeout) {
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
try {
|
||||
rmiRegistrationCallbacks.wait(connectionTimeout);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Interrupted waiting for RMI to finish.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"})
|
||||
/**
|
||||
* Internal call CLIENT ONLY.
|
||||
* <p>
|
||||
* RMI methods are usually created during the connection phase. If there are none, we should unblock the waiting client.connect().
|
||||
*/
|
||||
boolean rmiCallbacksIsEmpty() {
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
return rmiRegistrationCallbacks.size == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal call CLIENT ONLY.
|
||||
* <p>
|
||||
* RMI methods are usually created during the connection phase. If there are none, we should unblock the waiting client.connect().
|
||||
*/
|
||||
void rmiCallbacksNotify() {
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
rmiRegistrationCallbacks.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal call CLIENT ONLY.
|
||||
* <p>
|
||||
* RMI methods are usually created during the connection phase. If there are none, we should unblock the waiting client.connect().
|
||||
*/
|
||||
private
|
||||
void rmiCallbacksNotifyIfEmpty() {
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
if (rmiRegistrationCallbacks.size == 0) {
|
||||
rmiRegistrationCallbacks.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"})
|
||||
@Override
|
||||
public final
|
||||
<Iface, Impl extends Iface> Iface createProxyObject(final Class<Impl> remoteImplementationClass) throws IOException {
|
||||
// only one register can happen at a time
|
||||
synchronized (remoteObjectLock) {
|
||||
objectRegistrationLatch = new ObjectRegistrationLatch();
|
||||
<Iface> void getRemoteObject(final Class<Iface> interfaceClass, final RemoteObjectCallback<Iface> callback) throws IOException {
|
||||
if (!interfaceClass.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot create a proxy for RMI access. It must be an interface.");
|
||||
}
|
||||
|
||||
RmiRegistration message;
|
||||
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
int nextRmiID = rmiRegistrationID++;
|
||||
rmiRegistrationCallbacks.put(nextRmiID, callback);
|
||||
message = new RmiRegistration(interfaceClass, nextRmiID);
|
||||
}
|
||||
|
||||
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
|
||||
// have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here.
|
||||
|
||||
// since this synchronous, we want to wait for the response before we continue
|
||||
// this means we are creating a NEW object on the server, bound access to only this connection
|
||||
TCP(new RmiRegistration(remoteImplementationClass.getName())).flush();
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
if (!objectRegistrationLatch.latch.await(2, TimeUnit.SECONDS)) {
|
||||
final String errorMessage = "Timed out getting registration ID for: " + remoteImplementationClass;
|
||||
logger.error(errorMessage);
|
||||
throw new IOException(errorMessage);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
final String errorMessage = "Error getting registration ID for: " + remoteImplementationClass;
|
||||
logger.error(errorMessage, e);
|
||||
throw new IOException(errorMessage, e);
|
||||
TCP(message).flush();
|
||||
}
|
||||
|
||||
// local var to prevent double hit on volatile field
|
||||
final ObjectRegistrationLatch latch = objectRegistrationLatch;
|
||||
if (latch.hasError) {
|
||||
final String errorMessage = "Error getting registration ID for: " + remoteImplementationClass;
|
||||
logger.error(errorMessage);
|
||||
throw new IOException(errorMessage);
|
||||
}
|
||||
|
||||
return (Iface) latch.remoteObject;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"})
|
||||
@SuppressWarnings({"UnnecessaryLocalVariable", "unchecked", "Duplicates"})
|
||||
@Override
|
||||
public final
|
||||
<Iface, Impl extends Iface> Iface getProxyObject(final int objectId) throws IOException {
|
||||
// only one register can happen at a time
|
||||
synchronized (remoteObjectLock) {
|
||||
objectRegistrationLatch = new ObjectRegistrationLatch();
|
||||
<Iface> void getRemoteObject(final int objectId, final RemoteObjectCallback<Iface> callback) throws IOException {
|
||||
RmiRegistration message;
|
||||
|
||||
// since this synchronous, we want to wait for the response before we continue
|
||||
// this means that we are ACCESSING a remote object on the server, the server checks GLOBAL, then LOCAL for this object
|
||||
TCP(new RmiRegistration(objectId)).flush();
|
||||
|
||||
//noinspection Duplicates
|
||||
try {
|
||||
if (!objectRegistrationLatch.latch.await(2, TimeUnit.SECONDS)) {
|
||||
final String errorMessage = "Timed out getting registration for ID: " + objectId;
|
||||
logger.error(errorMessage);
|
||||
throw new IOException(errorMessage);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
final String errorMessage = "Error getting registration for ID: " + objectId;
|
||||
logger.error(errorMessage, e);
|
||||
throw new IOException(errorMessage, e);
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
int nextRmiID = rmiRegistrationID++;
|
||||
rmiRegistrationCallbacks.put(nextRmiID, callback);
|
||||
message = new RmiRegistration(objectId, nextRmiID);
|
||||
}
|
||||
|
||||
// local var to prevent double hit on volatile field
|
||||
final ObjectRegistrationLatch latch = objectRegistrationLatch;
|
||||
if (latch.hasError) {
|
||||
final String errorMessage = "Error getting registration for ID: " + objectId;
|
||||
logger.error(errorMessage);
|
||||
throw new IOException(errorMessage);
|
||||
}
|
||||
|
||||
return (Iface) latch.remoteObject;
|
||||
}
|
||||
// We use a callback to notify us when the object is ready. We can't "create this on the fly" because we
|
||||
// have to wait for the object to be created + ID to be assigned on the remote system BEFORE we can create the proxy instance here.
|
||||
|
||||
// this means we are creating a NEW object on the server, bound access to only this connection
|
||||
TCP(message).flush();
|
||||
}
|
||||
|
||||
final
|
||||
void registerInternal(final ConnectionImpl connection, final RmiRegistration remoteRegistration) {
|
||||
final String implementationClassName = remoteRegistration.remoteImplementationClass;
|
||||
final Class<?> interfaceClass = remoteRegistration.interfaceClass;
|
||||
final int rmiID = remoteRegistration.rmiID;
|
||||
|
||||
|
||||
if (implementationClassName != null) {
|
||||
if (interfaceClass != null) {
|
||||
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist)
|
||||
//
|
||||
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
|
||||
|
||||
Class<?> implementationClass;
|
||||
|
||||
try {
|
||||
implementationClass = Class.forName(implementationClassName);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error registering RMI class " + implementationClassName, e);
|
||||
connection.TCP(new RmiRegistration()).flush();
|
||||
// have to find the implementation from the specified interface
|
||||
CryptoSerializationManager manager = getEndPoint().serializationManager;
|
||||
KryoExtra kryo = manager.takeKryo();
|
||||
Registration registration = kryo.getRegistration(interfaceClass);
|
||||
|
||||
|
||||
if (registration == null) {
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
logger.error("Error getting RMI class interface for " + interfaceClass);
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
implementationClass = manager.getRmiImpl(registration.getId());
|
||||
if (implementationClass == null) {
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
logger.error("Error getting RMI class implementation for " + interfaceClass);
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Object remotePrimaryObject = implementationClass.newInstance();
|
||||
// this is what creates a new instance of the impl class, and stores it as an ID.
|
||||
final Object remotePrimaryObject = kryo.newInstance(implementationClass);
|
||||
|
||||
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
|
||||
manager.returnKryo(kryo);
|
||||
|
||||
rmiBridge.register(rmiBridge.nextObjectId(), remotePrimaryObject);
|
||||
|
||||
LinkedList<ClassObject> remoteClasses = new LinkedList<ClassObject>();
|
||||
@ -1055,7 +1106,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
while ((remoteClassObject = remoteClasses.pollFirst()) != null) {
|
||||
// we have to check for any additional fields that will have proxy information
|
||||
for (Field field : remoteClassObject.clazz.getDeclaredFields()) {
|
||||
if (field.getAnnotation(RMI.class) != null) {
|
||||
if (field.getAnnotation(Rmi.class) != null) {
|
||||
boolean prev = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
final Object o = field.get(remoteClassObject.object);
|
||||
@ -1069,10 +1120,10 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
}
|
||||
}
|
||||
|
||||
connection.TCP(new RmiRegistration(remotePrimaryObject)).flush();
|
||||
connection.TCP(new RmiRegistration(remotePrimaryObject, rmiID)).flush();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error registering RMI class " + implementationClassName, e);
|
||||
connection.TCP(new RmiRegistration()).flush();
|
||||
logger.error("Error registering RMI class " + implementationClass, e);
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
}
|
||||
}
|
||||
else if (remoteRegistration.remoteObjectId > RmiBridge.INVALID_RMI) {
|
||||
@ -1082,24 +1133,27 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
Object object = getImplementationObject(remoteRegistration.remoteObjectId);
|
||||
|
||||
if (object != null) {
|
||||
connection.TCP(new RmiRegistration(object)).flush();
|
||||
connection.TCP(new RmiRegistration(object, rmiID)).flush();
|
||||
} else {
|
||||
connection.TCP(new RmiRegistration()).flush();
|
||||
connection.TCP(new RmiRegistration(rmiID)).flush();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// THIS IS ON THE LOCAL CONNECTION (that sent the 'create proxy object') SIDE
|
||||
// THIS IS ON THE LOCAL CONNECTION SIDE, which is the side that called 'getRemoteObject()'
|
||||
|
||||
// the next two use a local var, so that there isn't a double hit for volatile access
|
||||
final ObjectRegistrationLatch latch = this.objectRegistrationLatch;
|
||||
latch.hasError = remoteRegistration.hasError;
|
||||
// this will be null if there was an error
|
||||
Object remoteObject = remoteRegistration.remoteObject;
|
||||
|
||||
if (!remoteRegistration.hasError) {
|
||||
latch.remoteObject = remoteRegistration.remoteObject;
|
||||
RemoteObjectCallback callback;
|
||||
synchronized (rmiRegistrationCallbacks) {
|
||||
callback = rmiRegistrationCallbacks.remove(remoteRegistration.rmiID);
|
||||
}
|
||||
|
||||
// notify the original register that it may continue. We access the volatile field directly, so that it's members are updated
|
||||
objectRegistrationLatch.latch.countDown();
|
||||
//noinspection unchecked
|
||||
callback.created(remoteObject);
|
||||
|
||||
// tell the client that we are finished with all RMI callbacks
|
||||
rmiCallbacksNotifyIfEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1109,6 +1163,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
* @return the registered ID for a specific object. This is used by the "local" side when setting up the to fetch an object for the
|
||||
* "remote" side for RMI
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
<T> int getRegisteredId(final T object) {
|
||||
// always check local before checking global, because less contention on the synchronization
|
||||
@ -1131,8 +1186,10 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
*
|
||||
* @param type must be the interface the proxy will bind to
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
RemoteObject getProxyObject(final int objectID, final Class<?> type) {
|
||||
synchronized (proxyIdCache) {
|
||||
// we want to have a connection specific cache of IDs, using weak references.
|
||||
// because this is PER CONNECTION, this is safe.
|
||||
RemoteObject remoteObject = proxyIdCache.get(objectID);
|
||||
@ -1145,10 +1202,12 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
|
||||
|
||||
return remoteObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by RMI for the REMOTE side, to get the implementation
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Object getImplementationObject(final int objectID) {
|
||||
if (RmiBridge.isGlobal(objectID)) {
|
||||
|
@ -38,10 +38,11 @@ import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import com.esotericsoftware.kryo.serializers.CollectionSerializer;
|
||||
import com.esotericsoftware.kryo.serializers.FieldSerializer;
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import com.esotericsoftware.kryo.util.IntMap;
|
||||
import com.esotericsoftware.kryo.util.MapReferenceResolver;
|
||||
|
||||
import dorkbox.network.connection.ping.PingMessage;
|
||||
import dorkbox.network.rmi.CachedMethod;
|
||||
import dorkbox.network.rmi.InvocationHandlerSerializer;
|
||||
import dorkbox.network.rmi.InvocationResultSerializer;
|
||||
import dorkbox.network.rmi.InvokeMethod;
|
||||
@ -49,11 +50,10 @@ import dorkbox.network.rmi.InvokeMethodResult;
|
||||
import dorkbox.network.rmi.InvokeMethodSerializer;
|
||||
import dorkbox.network.rmi.RemoteObjectSerializer;
|
||||
import dorkbox.network.rmi.RmiRegistration;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.network.util.RmiSerializationManager;
|
||||
import dorkbox.objectPool.ObjectPool;
|
||||
import dorkbox.objectPool.PoolableObject;
|
||||
import dorkbox.util.Property;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.serialization.ArraysAsListSerializer;
|
||||
import dorkbox.util.serialization.EccPrivateKeySerializer;
|
||||
import dorkbox.util.serialization.EccPublicKeySerializer;
|
||||
@ -70,9 +70,9 @@ import io.netty.buffer.ByteBuf;
|
||||
*/
|
||||
@SuppressWarnings({"unused", "StaticNonFinalField"})
|
||||
public
|
||||
class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
class CryptoSerializationManager implements dorkbox.network.util.CryptoSerializationManager, RmiSerializationManager {
|
||||
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(KryoCryptoSerializationManager.class);
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CryptoSerializationManager.class);
|
||||
|
||||
/**
|
||||
* Specify if we want KRYO to use unsafe memory for serialization, or to use the ASM backend. Unsafe memory use is WAY faster, but is
|
||||
@ -84,18 +84,18 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
private static final String OBJECT_ID = "objectID";
|
||||
|
||||
public static
|
||||
KryoCryptoSerializationManager DEFAULT() {
|
||||
CryptoSerializationManager DEFAULT() {
|
||||
return DEFAULT(true, true);
|
||||
}
|
||||
|
||||
public static
|
||||
KryoCryptoSerializationManager DEFAULT(final boolean references, final boolean registrationRequired) {
|
||||
CryptoSerializationManager DEFAULT(final boolean references, final boolean registrationRequired) {
|
||||
// ignore fields that have the "@IgnoreSerialization" annotation.
|
||||
Collection<Class<? extends Annotation>> marks = new ArrayList<Class<? extends Annotation>>();
|
||||
marks.add(IgnoreSerialization.class);
|
||||
SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
|
||||
|
||||
final KryoCryptoSerializationManager serializationManager = new KryoCryptoSerializationManager(references,
|
||||
final CryptoSerializationManager serializationManager = new CryptoSerializationManager(references,
|
||||
registrationRequired,
|
||||
disregardingFactory);
|
||||
|
||||
@ -106,7 +106,7 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
serializationManager.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
|
||||
serializationManager.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
|
||||
serializationManager.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
|
||||
serializationManager.register(dorkbox.network.connection.registration.Registration.class);
|
||||
serializationManager.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!
|
||||
|
||||
// necessary for the transport of exceptions.
|
||||
serializationManager.register(ArrayList.class, new CollectionSerializer());
|
||||
@ -144,11 +144,19 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
private static class RemoteClass<Iface, Impl extends Iface> {
|
||||
private final Class<Iface> ifaceClass;
|
||||
private final Class<Impl> implClass;
|
||||
private static class RemoteIfaceClass {
|
||||
private final Class<?> ifaceClass;
|
||||
|
||||
RemoteClass(final Class<Iface> ifaceClass, final Class<Impl> implClass) {
|
||||
RemoteIfaceClass(final Class<?> ifaceClass) {
|
||||
this.ifaceClass = ifaceClass;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteImplClass {
|
||||
private final Class<?> ifaceClass;
|
||||
private final Class<?> implClass;
|
||||
|
||||
RemoteImplClass(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
this.ifaceClass = ifaceClass;
|
||||
this.implClass = implClass;
|
||||
}
|
||||
@ -157,18 +165,21 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
private boolean initialized = false;
|
||||
private final ObjectPool<KryoExtra> kryoPool;
|
||||
|
||||
// used by operations performed during kryo initialization, which are by default package access (since it's an anon-inner class
|
||||
private final List<Class<?>> classesToRegister = new ArrayList<Class<?>>();
|
||||
private final List<ClassSerializer> classSerializerToRegister = new ArrayList<ClassSerializer>();
|
||||
private final List<ClassSerializer2> classSerializer2ToRegister = new ArrayList<ClassSerializer2>();
|
||||
private final List<RemoteClass> remoteClassToRegister = new ArrayList<RemoteClass>();
|
||||
// used by operations performed during kryo initialization, which are by default package access (since it's an anon-inner class)
|
||||
// All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
|
||||
// Object checking is performed during actual registration.
|
||||
private final List<Object> classesToRegister = new ArrayList<Object>();
|
||||
|
||||
private boolean shouldInitRMI = false;
|
||||
private boolean usesRmi = false;
|
||||
private InvokeMethodSerializer methodSerializer = null;
|
||||
private Serializer<Object> invocationSerializer = null;
|
||||
private RemoteObjectSerializer remoteObjectSerializer;
|
||||
|
||||
|
||||
// used to track which interface -> implementation, for use by RMI
|
||||
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
|
||||
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiIfacetoImpl = new IdentityMap<Class<?>, Class<?>>();
|
||||
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
|
||||
|
||||
/**
|
||||
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
|
||||
@ -195,61 +206,76 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
* <p>
|
||||
*/
|
||||
public
|
||||
KryoCryptoSerializationManager(final boolean references, final boolean registrationRequired, final SerializerFactory factory) {
|
||||
|
||||
CryptoSerializationManager(final boolean references, final boolean registrationRequired, final SerializerFactory factory) {
|
||||
kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
|
||||
@Override
|
||||
public
|
||||
KryoExtra create() {
|
||||
synchronized (KryoCryptoSerializationManager.this) {
|
||||
KryoExtra kryo = new KryoExtra();
|
||||
synchronized (CryptoSerializationManager.this) {
|
||||
KryoExtra kryo = new KryoExtra(CryptoSerializationManager.this);
|
||||
|
||||
// we HAVE to pre-allocate the KRYOs
|
||||
boolean useAsm = !useUnsafeMemory;
|
||||
|
||||
kryo.setAsmEnabled(useAsm);
|
||||
kryo.getFieldSerializerConfig().setUseAsm(useAsm);
|
||||
kryo.setRegistrationRequired(registrationRequired);
|
||||
|
||||
kryo.setReferences(references);
|
||||
|
||||
for (Class<?> clazz : classesToRegister) {
|
||||
kryo.register(clazz);
|
||||
}
|
||||
|
||||
for (ClassSerializer classSerializer : classSerializerToRegister) {
|
||||
kryo.register(classSerializer.clazz, classSerializer.serializer);
|
||||
}
|
||||
|
||||
for (ClassSerializer2 classSerializer : classSerializer2ToRegister) {
|
||||
kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id);
|
||||
}
|
||||
|
||||
if (shouldInitRMI) {
|
||||
if (usesRmi) {
|
||||
kryo.register(Class.class);
|
||||
kryo.register(RmiRegistration.class);
|
||||
kryo.register(InvokeMethod.class, methodSerializer);
|
||||
kryo.register(Object[].class);
|
||||
|
||||
// This has to be PER KRYO,
|
||||
// This has to be for each kryo instance!
|
||||
InvocationResultSerializer resultSerializer = new InvocationResultSerializer(kryo);
|
||||
resultSerializer.removeField(OBJECT_ID);
|
||||
|
||||
kryo.register(InvokeMethodResult.class, resultSerializer);
|
||||
kryo.register(InvocationHandler.class, invocationSerializer);
|
||||
}
|
||||
|
||||
for (RemoteClass remoteClass : remoteClassToRegister) {
|
||||
kryo.register(remoteClass.implClass, remoteObjectSerializer);
|
||||
|
||||
// After all common registrations, register OtherObjectImpl only on the server using the remote object interface ID.
|
||||
// This causes OtherObjectImpl to be serialized as OtherObject.
|
||||
int otherObjectID = kryo.getRegistration(remoteClass.implClass)
|
||||
.getId();
|
||||
// All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
|
||||
for (Object clazz : classesToRegister) {
|
||||
if (clazz instanceof Class) {
|
||||
kryo.register((Class)clazz);
|
||||
}
|
||||
else if (clazz instanceof ClassSerializer) {
|
||||
ClassSerializer classSerializer = (ClassSerializer) clazz;
|
||||
kryo.register(classSerializer.clazz, classSerializer.serializer);
|
||||
}
|
||||
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
|
||||
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
|
||||
|
||||
// this overrides the 'otherObjectID' with the specified class/serializer, so that when we WRITE this ID, the impl is READ
|
||||
kryo.register(remoteClass.ifaceClass, remoteObjectSerializer, otherObjectID);
|
||||
// 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
|
||||
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
|
||||
|
||||
// we have to save the fact that we might have overridden methods
|
||||
CachedMethod.registerOverridden(remoteClass.ifaceClass, remoteClass.implClass);
|
||||
// registers the implementation, so that when it is written, it becomes a "magic" proxy object
|
||||
int id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
|
||||
|
||||
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
|
||||
// if this is over-written, we don't care.
|
||||
rmiIdToImpl.put(id, remoteImplClass.implClass);
|
||||
rmiIdToIface.put(id, remoteImplClass.ifaceClass);
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,11 +299,13 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
SerializationManager register(Class<?> clazz) {
|
||||
RmiSerializationManager register(Class<?> clazz) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate register(Class) call.");
|
||||
}
|
||||
else {
|
||||
else if (clazz.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an interface for serialization. It must be an implementation.");
|
||||
} else {
|
||||
classesToRegister.add(clazz);
|
||||
}
|
||||
|
||||
@ -293,12 +321,15 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
SerializationManager register(Class<?> clazz, Serializer<?> serializer) {
|
||||
RmiSerializationManager register(Class<?> clazz, Serializer<?> serializer) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate register(Class, Serializer) call.");
|
||||
}
|
||||
else if (clazz.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an interface for serialization. It must be an implementation.");
|
||||
}
|
||||
else {
|
||||
classSerializerToRegister.add(new ClassSerializer(clazz, serializer));
|
||||
classesToRegister.add(new ClassSerializer(clazz, serializer));
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -316,52 +347,99 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
SerializationManager register(Class<?> clazz, Serializer<?> serializer, int id) {
|
||||
RmiSerializationManager register(Class<?> clazz, Serializer<?> serializer, int id) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate register(Class, Serializer, int) call.");
|
||||
}
|
||||
else if (clazz.isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot register an interface for serialization. It must be an implementation.");
|
||||
}
|
||||
else {
|
||||
classSerializer2ToRegister.add(new ClassSerializer2(clazz, serializer, id));
|
||||
classesToRegister.add(new ClassSerializer2(clazz, serializer, id));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Objects that are accessed over RMI, must be accessed via an interface. This method configures the serialization of an implementation
|
||||
* to be serialized via the defined interface, as a RemoteObject (ie: proxy object). If the implementation class is ALREADY registered,
|
||||
* then it's registration will be overwritten by this one
|
||||
*
|
||||
* @param ifaceClass The interface used to access the remote object
|
||||
* @param implClass The implementation class of the interface
|
||||
* Enable remote method invocation (RMI) for this connection. 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. If the
|
||||
* type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
<Iface, Impl extends Iface> void registerRemote(final Class<Iface> ifaceClass, final Class<Impl> implClass) {
|
||||
RmiSerializationManager registerRmiInterface(Class<?> ifaceClass) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class, Class) call.");
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
|
||||
remoteClassToRegister.add(new RemoteClass<Iface, Impl>(ifaceClass, implClass));
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* NOTE: You must have ALREADY registered the implementation class. This method just enables the proxy magic.
|
||||
*
|
||||
* @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) {
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class, Class) call.");
|
||||
return this;
|
||||
}
|
||||
|
||||
usesRmi = true;
|
||||
classesToRegister.add(new RemoteImplClass(ifaceClass, implClass));
|
||||
|
||||
// this MUST BE UNIQUE otherwise unexpected things can happen.
|
||||
Class<?> a = this.rmiIfacetoImpl.put(ifaceClass, implClass);
|
||||
Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);
|
||||
|
||||
// this MUST BE UNIQUE otherwise unexpected things can happen.
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
|
||||
*
|
||||
* @return true if there are classes that have been registered for RMI
|
||||
*/
|
||||
@Override
|
||||
public synchronized
|
||||
void initRmiSerialization() {
|
||||
boolean initRmiSerialization() {
|
||||
if (!usesRmi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
logger.warn("Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
methodSerializer = new InvokeMethodSerializer();
|
||||
invocationSerializer = new InvocationHandlerSerializer(logger);
|
||||
remoteObjectSerializer = new RemoteObjectSerializer();
|
||||
|
||||
shouldInitRMI = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,6 +449,10 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
public synchronized
|
||||
void finishInit() {
|
||||
initialized = true;
|
||||
|
||||
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
|
||||
// correctly and (if not) we are are notified on the initial thread (instead of on the network udpate thread)
|
||||
kryoPool.put(takeKryo());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -379,6 +461,53 @@ class KryoCryptoSerializationManager implements CryptoSerializationManager {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(int objectId) {
|
||||
return rmiIdToIface.get(objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
*
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiImpl(int objectId) {
|
||||
return rmiIdToImpl.get(objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified interface
|
||||
*
|
||||
* @return the corresponding implementation
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiImpl(Class<?> iface) {
|
||||
return rmiIfacetoImpl.get(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
Class<?> getRmiIface(Class<?> implementation) {
|
||||
return rmiImplToIface.get(implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return takes a kryo instance from the pool.
|
||||
*/
|
@ -45,7 +45,6 @@ import dorkbox.network.pipeline.KryoEncoderCrypto;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.network.store.NullSettingsStore;
|
||||
import dorkbox.network.store.SettingsStore;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.Property;
|
||||
import dorkbox.util.crypto.CryptoECC;
|
||||
@ -156,7 +155,7 @@ class EndPoint<C extends Connection> {
|
||||
protected final Class<? extends EndPoint<C>> type;
|
||||
|
||||
protected final ConnectionManager<C> connectionManager;
|
||||
protected final CryptoSerializationManager serializationManager;
|
||||
protected final dorkbox.network.util.CryptoSerializationManager serializationManager;
|
||||
protected final RegistrationWrapper<C> registrationWrapper;
|
||||
|
||||
protected final Object shutdownInProgress = new Object();
|
||||
@ -221,14 +220,11 @@ class EndPoint<C extends Connection> {
|
||||
if (options.serialization != null) {
|
||||
this.serializationManager = options.serialization;
|
||||
} else {
|
||||
this.serializationManager = KryoCryptoSerializationManager.DEFAULT();
|
||||
this.serializationManager = CryptoSerializationManager.DEFAULT();
|
||||
}
|
||||
|
||||
rmiEnabled = options.rmiEnabled;
|
||||
if (rmiEnabled) {
|
||||
// setup our RMI serialization managers. Can only be called once
|
||||
serializationManager.initRmiSerialization();
|
||||
}
|
||||
rmiEnabled = serializationManager.initRmiSerialization();
|
||||
rmiExecutor = options.rmiExecutor;
|
||||
|
||||
|
||||
@ -423,7 +419,7 @@ class EndPoint<C extends Connection> {
|
||||
* Returns the serialization wrapper if there is an object type that needs to be added outside of the basics.
|
||||
*/
|
||||
public
|
||||
CryptoSerializationManager getSerialization() {
|
||||
dorkbox.network.util.CryptoSerializationManager getSerialization() {
|
||||
return this.serializationManager;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,13 @@
|
||||
*/
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.network.Client;
|
||||
import dorkbox.network.Configuration;
|
||||
import dorkbox.network.connection.bridge.ConnectionBridge;
|
||||
@ -22,12 +29,6 @@ import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* This serves the purpose of making sure that specific methods are not available to the end user.
|
||||
@ -38,10 +39,12 @@ class EndPointClient<C extends Connection> extends EndPoint<C> implements Runnab
|
||||
protected C connection;
|
||||
|
||||
protected final Object registrationLock = new Object();
|
||||
|
||||
protected final AtomicInteger connectingBootstrap = new AtomicInteger(0);
|
||||
protected List<BootstrapWrapper> bootstraps = new LinkedList<BootstrapWrapper>();
|
||||
protected volatile int connectionTimeout = 5000; // default
|
||||
protected volatile boolean registrationComplete = false;
|
||||
private volatile boolean rmiInitializationComplete = false;
|
||||
|
||||
private volatile ConnectionBridge connectionBridgeFlushAlways;
|
||||
|
||||
@ -186,6 +189,11 @@ class EndPointClient<C extends Connection> extends EndPoint<C> implements Runnab
|
||||
}
|
||||
};
|
||||
|
||||
//noinspection unchecked
|
||||
this.connection = (C) connection;
|
||||
|
||||
// check if there were any RMI callbacks during the connect phase.
|
||||
rmiInitializationComplete = connection.rmiCallbacksIsEmpty();
|
||||
|
||||
// notify the registration we are done!
|
||||
synchronized (this.registrationLock) {
|
||||
@ -193,6 +201,21 @@ class EndPointClient<C extends Connection> extends EndPoint<C> implements Runnab
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal call.
|
||||
* <p>
|
||||
* RMI methods are usually created during the connection phase. We should wait until they are finished, but ONLY if there is
|
||||
* something we need to wait for.
|
||||
*
|
||||
* This is called AFTER registration is finished.
|
||||
*/
|
||||
protected
|
||||
void waitForRmi(final int connectionTimeout) {
|
||||
if (!rmiInitializationComplete && connection instanceof ConnectionImpl) {
|
||||
((ConnectionImpl) connection).waitForRmi(connectionTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose methods to send objects to a destination.
|
||||
* <p/>
|
||||
@ -224,6 +247,11 @@ class EndPointClient<C extends Connection> extends EndPoint<C> implements Runnab
|
||||
this.registrationLock.notify();
|
||||
}
|
||||
registrationComplete = false;
|
||||
|
||||
// Always unblock the waiting client.connect().
|
||||
if (connection instanceof ConnectionImpl) {
|
||||
((ConnectionImpl) connection).rmiCallbacksNotify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,6 +261,11 @@ class EndPointClient<C extends Connection> extends EndPoint<C> implements Runnab
|
||||
synchronized (this.registrationLock) {
|
||||
this.registrationLock.notify();
|
||||
}
|
||||
|
||||
// Always unblock the waiting client.connect().
|
||||
if (connection instanceof ConnectionImpl) {
|
||||
((ConnectionImpl) connection).rmiCallbacksNotify();
|
||||
}
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,14 @@
|
||||
*/
|
||||
package dorkbox.network.connection;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
|
||||
import dorkbox.network.pipeline.ByteBufInput;
|
||||
import dorkbox.network.pipeline.ByteBufOutput;
|
||||
import dorkbox.util.bytes.BigEndian;
|
||||
@ -26,11 +33,6 @@ import io.netty.buffer.Unpooled;
|
||||
import net.jpountz.lz4.LZ4Compressor;
|
||||
import net.jpountz.lz4.LZ4Factory;
|
||||
import net.jpountz.lz4.LZ4FastDecompressor;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Nothing in this class is thread safe
|
||||
@ -83,8 +85,11 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
|
||||
private byte[] decompressOutput;
|
||||
private ByteBuf decompressBuf;
|
||||
|
||||
private dorkbox.network.util.CryptoSerializationManager serializationManager;
|
||||
|
||||
public
|
||||
KryoExtra() {
|
||||
KryoExtra(final dorkbox.network.util.CryptoSerializationManager serializationManager) {
|
||||
this.serializationManager = serializationManager;
|
||||
}
|
||||
|
||||
public synchronized
|
||||
@ -580,4 +585,9 @@ class KryoExtra<C extends ICryptoConnection> extends Kryo {
|
||||
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
public
|
||||
dorkbox.network.util.CryptoSerializationManager getSerializationManager() {
|
||||
return serializationManager;
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,6 @@ import org.bouncycastle.crypto.params.IESParameters;
|
||||
*/
|
||||
public
|
||||
class Registration {
|
||||
public static final byte notAdroid = (byte) 0;
|
||||
public static final byte android = (byte) 1;
|
||||
|
||||
|
||||
// signals which serialization is possible. If they match, then UNSAFE can be used (except android. it always must use ASM)
|
||||
public byte connectionType;
|
||||
|
||||
public ECPublicKeyParameters publicKey;
|
||||
public IESParameters eccParameters;
|
||||
public byte[] aesKey;
|
||||
|
@ -15,16 +15,22 @@
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.network.Broadcast;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.RegistrationWrapper;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import dorkbox.network.connection.wrapper.UdpWrapper;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.bytes.OptimizeUtilsByteArray;
|
||||
import dorkbox.util.crypto.CryptoAES;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@ -34,12 +40,6 @@ import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
@Sharable
|
||||
public
|
||||
@ -50,13 +50,13 @@ class RegistrationRemoteHandlerServerUDP<C extends Connection> extends MessageTo
|
||||
private final org.slf4j.Logger logger;
|
||||
private final ByteBuf discoverResponseBuffer;
|
||||
private final RegistrationWrapper<C> registrationWrapper;
|
||||
private final CryptoSerializationManager serializationManager;
|
||||
private final dorkbox.network.util.CryptoSerializationManager serializationManager;
|
||||
|
||||
|
||||
public
|
||||
RegistrationRemoteHandlerServerUDP(final String name,
|
||||
final RegistrationWrapper<C> registrationWrapper,
|
||||
final CryptoSerializationManager serializationManager) {
|
||||
final dorkbox.network.util.CryptoSerializationManager serializationManager) {
|
||||
final String name1 = name + " Registration-UDP-Server";
|
||||
this.logger = org.slf4j.LoggerFactory.getLogger(name1);
|
||||
this.registrationWrapper = registrationWrapper;
|
||||
@ -170,9 +170,9 @@ class RegistrationRemoteHandlerServerUDP<C extends Connection> extends MessageTo
|
||||
// registration is the ONLY thing NOT encrypted
|
||||
Logger logger2 = this.logger;
|
||||
RegistrationWrapper<C> registrationWrapper2 = this.registrationWrapper;
|
||||
CryptoSerializationManager serializationManager2 = this.serializationManager;
|
||||
dorkbox.network.util.CryptoSerializationManager serializationManager2 = this.serializationManager;
|
||||
|
||||
if (KryoCryptoSerializationManager.isEncrypted(message)) {
|
||||
if (CryptoSerializationManager.isEncrypted(message)) {
|
||||
// we need to FORWARD this message "down the pipeline".
|
||||
|
||||
ConnectionImpl connection = registrationWrapper2.getServerUDP(udpRemoteAddress);
|
||||
|
@ -34,14 +34,14 @@
|
||||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* An {@link InputStream} which reads data from a {@link ByteBuf}.
|
||||
|
@ -15,20 +15,18 @@
|
||||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.rmi.OverriddenMethods;
|
||||
import dorkbox.network.rmi.RemoteObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
public
|
||||
class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
|
||||
|
||||
private static final RmiFieldCache fieldCache = RmiFieldCache.INSTANCE();
|
||||
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
|
||||
|
||||
public
|
||||
LocalRmiDecoder() {
|
||||
@ -70,7 +68,10 @@ class LocalRmiDecoder extends MessageToMessageDecoder<Object> {
|
||||
throw new RuntimeException("Unable to get RMI interface object for RMI implementation");
|
||||
}
|
||||
|
||||
final Class<?> iface = overriddenMethods.getReverse(localRmiObject.getClass());
|
||||
|
||||
final Class<?> iface = connection.getEndPoint()
|
||||
.getSerialization()
|
||||
.getRmiIface(localRmiObject.getClass());
|
||||
if (iface == null) {
|
||||
throw new RuntimeException("Unable to get interface for RMI implementation");
|
||||
}
|
||||
|
@ -15,20 +15,20 @@
|
||||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
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;
|
||||
|
||||
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.EndPoint;
|
||||
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;
|
||||
|
||||
@Sharable
|
||||
public
|
||||
class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
|
||||
@ -66,7 +66,7 @@ class LocalRmiEncoder extends MessageToMessageEncoder<Object> {
|
||||
Boolean needsTransform = transformObjectCache.get(implClass);
|
||||
|
||||
if (needsTransform == null) {
|
||||
boolean hasRmi = implClass.getAnnotation(RMI.class) != null;
|
||||
boolean hasRmi = implClass.getAnnotation(Rmi.class) != null;
|
||||
|
||||
if (hasRmi) {
|
||||
// replace object
|
||||
|
@ -15,13 +15,14 @@
|
||||
*/
|
||||
package dorkbox.network.pipeline;
|
||||
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import dorkbox.network.rmi.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
|
||||
*/
|
||||
@ -57,7 +58,7 @@ class RmiFieldCache {
|
||||
final ArrayList<Field> fields = new ArrayList<Field>();
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getAnnotation(RMI.class) != null) {
|
||||
if (field.getAnnotation(Rmi.class) != null) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
@ -15,26 +15,26 @@
|
||||
*/
|
||||
package dorkbox.network.pipeline.udp;
|
||||
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Sharable
|
||||
public
|
||||
class KryoDecoderUdp extends MessageToMessageDecoder<DatagramPacket> {
|
||||
|
||||
private final CryptoSerializationManager serializationManager;
|
||||
private final dorkbox.network.util.CryptoSerializationManager serializationManager;
|
||||
|
||||
public
|
||||
KryoDecoderUdp(CryptoSerializationManager serializationManager) {
|
||||
KryoDecoderUdp(dorkbox.network.util.CryptoSerializationManager serializationManager) {
|
||||
this.serializationManager = serializationManager;
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ class KryoDecoderUdp extends MessageToMessageDecoder<DatagramPacket> {
|
||||
// there is a REMOTE possibility that UDP traffic BEAT the TCP registration traffic, which means that THIS packet
|
||||
// COULD be encrypted!
|
||||
|
||||
if (KryoCryptoSerializationManager.isEncrypted(data)) {
|
||||
if (CryptoSerializationManager.isEncrypted(data)) {
|
||||
String message = "Encrypted UDP packet received before registration complete.";
|
||||
LoggerFactory.getLogger(this.getClass()).error(message);
|
||||
throw new IOException(message);
|
||||
|
@ -34,19 +34,6 @@
|
||||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import com.esotericsoftware.kryo.util.Util;
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.util.RMISerializationManager;
|
||||
import dorkbox.util.ClassHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -56,6 +43,22 @@ import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
import com.esotericsoftware.kryo.util.Util;
|
||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.network.util.RmiSerializationManager;
|
||||
import dorkbox.util.ClassHelper;
|
||||
|
||||
public
|
||||
class CachedMethod {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CachedMethod.class);
|
||||
@ -94,27 +97,34 @@ class CachedMethod {
|
||||
|
||||
// the purpose of the method cache, is to accelerate looking up methods for specific class
|
||||
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
|
||||
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
|
||||
|
||||
|
||||
// type will be likely be the interface
|
||||
public static
|
||||
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type) {
|
||||
/**
|
||||
* Called when we read a RMI method invocation on the "server" side (by kryo)
|
||||
*
|
||||
* @param type is the implementation type
|
||||
*/
|
||||
static
|
||||
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type, final int classId) {
|
||||
CachedMethod[] cachedMethods = methodCache.get(type);
|
||||
if (cachedMethods != null) {
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
cachedMethods = getCachedMethods(kryo, type);
|
||||
cachedMethods = getCachedMethods(kryo, type, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
|
||||
// type will be likely be the interface
|
||||
public static
|
||||
CachedMethod[] getMethods(final RMISerializationManager serializationManager, final Class<?> type) {
|
||||
/**
|
||||
* Called when we write an RMI method invocation on the "client" side (by RmiProxyHandler)
|
||||
*
|
||||
* @param type this is the interface.
|
||||
*/
|
||||
static
|
||||
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type, final int classId) {
|
||||
CachedMethod[] cachedMethods = methodCache.get(type);
|
||||
if (cachedMethods != null) {
|
||||
return cachedMethods;
|
||||
@ -122,7 +132,7 @@ class CachedMethod {
|
||||
|
||||
final KryoExtra kryo = serializationManager.takeKryo();
|
||||
try {
|
||||
cachedMethods = getCachedMethods(kryo, type);
|
||||
cachedMethods = getCachedMethods(kryo, type, classId);
|
||||
methodCache.put(type, cachedMethods);
|
||||
} finally {
|
||||
serializationManager.returnKryo(kryo);
|
||||
@ -131,10 +141,25 @@ class CachedMethod {
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
private static
|
||||
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type) {
|
||||
// race-conditions are OK, because we just recreate the same thing.
|
||||
final ArrayList<Method> methods = getMethods(type);
|
||||
private static
|
||||
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type, final int classId) {
|
||||
// sometimes, the method index is based upon an interface and NOT the implementation. We have to clear that up here.
|
||||
CryptoSerializationManager serialization = ((KryoExtra) kryo).getSerializationManager();
|
||||
|
||||
// when there is an interface available, we want to use that instead of the implementation. This is because the incoming
|
||||
// implementation is ACTUALLY mapped (on the "client" side) to the interface. If we don't use the interface, we will have the
|
||||
// wrong order of methods, so invoking a method by it's index will fail.
|
||||
Class<?> interfaceClass = serialization.getRmiIface(classId);
|
||||
|
||||
final ArrayList<Method> methods;
|
||||
|
||||
if (interfaceClass == null) {
|
||||
methods = getMethods(type);
|
||||
} else {
|
||||
methods = getMethods(interfaceClass);
|
||||
}
|
||||
|
||||
final int size = methods.size();
|
||||
final CachedMethod[] cachedMethods = new CachedMethod[size];
|
||||
|
||||
@ -153,8 +178,17 @@ class CachedMethod {
|
||||
|
||||
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
|
||||
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
|
||||
final IdentityMap<Method, Method> overriddenMethods = getOverriddenMethods(type, methods);
|
||||
final boolean asmEnabled = kryo.getAsmEnabled();
|
||||
|
||||
// will only be valid if implementation type is NOT NULL (otherwise will be null)
|
||||
final IdentityMap<Method, Method> overriddenMethods;
|
||||
if (interfaceClass == null) {
|
||||
overriddenMethods = null;
|
||||
} else {
|
||||
// type here must be the implementation
|
||||
overriddenMethods = getOverriddenMethods(type, methods);
|
||||
}
|
||||
|
||||
final boolean asmEnabled = kryo.getFieldSerializerConfig().isUseAsm();
|
||||
|
||||
MethodAccess methodAccess = null;
|
||||
// reflectASM can't get any method from the 'Object' object, and it MUST be public
|
||||
@ -171,6 +205,7 @@ class CachedMethod {
|
||||
for (int i = 0; i < size; i++) {
|
||||
final Method origMethod = methods.get(i);
|
||||
Method method = origMethod; // copy because one or more can be overridden
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
MethodAccess localMethodAccess = methodAccess; // copy because one or more can be overridden
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Class<?>[] asmParameterTypes = parameterTypes;
|
||||
@ -182,7 +217,7 @@ class CachedMethod {
|
||||
// the correct object for this overridden method to be called on.
|
||||
method = overriddenMethod;
|
||||
|
||||
Class<?> overrideType = method.getDeclaringClass();
|
||||
Class<?> overrideType = declaringClass;
|
||||
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
|
||||
localMethodAccess = MethodAccess.get(overrideType);
|
||||
asmParameterTypes = method.getParameterTypes();
|
||||
@ -201,7 +236,7 @@ class CachedMethod {
|
||||
cachedMethod = asmCachedMethod;
|
||||
} catch (Exception e) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", method.getDeclaringClass(), method.getName(), e);
|
||||
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, method.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,7 +246,18 @@ class CachedMethod {
|
||||
}
|
||||
cachedMethod.method = method;
|
||||
cachedMethod.origMethod = origMethod;
|
||||
cachedMethod.methodClassID = kryo.getRegistration(method.getDeclaringClass()).getId();
|
||||
|
||||
|
||||
// on the "server", we only register the implementation. NOT THE INTERFACE, so for RMI classes, we have to get the impl
|
||||
Class<?> impl = serialization.getRmiImpl(declaringClass);
|
||||
if (impl != null) {
|
||||
cachedMethod.methodClassID = kryo.getRegistration(impl)
|
||||
.getId();
|
||||
}
|
||||
else {
|
||||
cachedMethod.methodClassID = kryo.getRegistration(declaringClass)
|
||||
.getId();
|
||||
}
|
||||
cachedMethod.methodIndex = i;
|
||||
|
||||
// Store the serializer for each final parameter.
|
||||
@ -229,12 +275,10 @@ class CachedMethod {
|
||||
return cachedMethods;
|
||||
}
|
||||
|
||||
// does not null check
|
||||
private static
|
||||
IdentityMap<Method, Method> getOverriddenMethods(final Class<?> type, final ArrayList<Method> origMethods) {
|
||||
final Class<?> implType = overriddenMethods.get(type);
|
||||
|
||||
if (implType != null) {
|
||||
final ArrayList<Method> implMethods = getMethods(implType);
|
||||
final ArrayList<Method> implMethods = getMethods(type);
|
||||
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
|
||||
|
||||
for (Method origMethod : origMethods) {
|
||||
@ -274,10 +318,6 @@ class CachedMethod {
|
||||
|
||||
return overrideMap;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static
|
||||
ArrayList<Method> getMethods(final Class<?> type) {
|
||||
@ -312,14 +352,6 @@ class CachedMethod {
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the SerializationManager, so that RMI classes that are overridden for serialization purposes, can check to see if certain
|
||||
* methods need to be overridden.
|
||||
*/
|
||||
public static
|
||||
void registerOverridden(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
overriddenMethods.set(ifaceClass, implClass);
|
||||
}
|
||||
|
||||
public Method method;
|
||||
public int methodClassID;
|
||||
|
@ -15,14 +15,16 @@
|
||||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
public
|
||||
class InvocationHandlerSerializer extends Serializer<Object> {
|
||||
@ -36,7 +38,7 @@ class InvocationHandlerSerializer extends Serializer<Object> {
|
||||
@Override
|
||||
public
|
||||
void write(Kryo kryo, Output output, Object object) {
|
||||
RemoteObjectInvocationHandler handler = (RemoteObjectInvocationHandler) Proxy.getInvocationHandler(object);
|
||||
RmiProxyHandler handler = (RmiProxyHandler) Proxy.getInvocationHandler(object);
|
||||
output.writeInt(handler.objectID, true);
|
||||
}
|
||||
|
||||
|
@ -54,16 +54,22 @@ 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);
|
||||
|
||||
output.writeInt(object.objectID, true);
|
||||
output.writeInt(object.cachedMethod.methodClassID, true);
|
||||
output.writeByte(object.cachedMethod.methodIndex);
|
||||
|
||||
Serializer[] serializers = object.cachedMethod.serializers;
|
||||
int length = serializers.length;
|
||||
|
||||
Object[] args = object.args;
|
||||
|
||||
int i = 0, n = serializers.length;
|
||||
for (; i < n; i++) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
Serializer serializer = serializers[i];
|
||||
|
||||
if (serializer != null) {
|
||||
kryo.writeObjectOrNull(output, args[i], serializer);
|
||||
}
|
||||
@ -87,17 +93,19 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
|
||||
.getType();
|
||||
|
||||
byte methodIndex = input.readByte();
|
||||
CachedMethod cachedMethod;
|
||||
try {
|
||||
invokeMethod.cachedMethod = CachedMethod.getMethods(kryo, methodClass)[methodIndex];
|
||||
cachedMethod = CachedMethod.getMethods(kryo, methodClass, methodClassID)[methodIndex];
|
||||
invokeMethod.cachedMethod = cachedMethod;
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
|
||||
}
|
||||
|
||||
CachedMethod cachedMethod = invokeMethod.cachedMethod;
|
||||
Serializer<?>[] serializers = cachedMethod.serializers;
|
||||
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
|
||||
Object[] args = new Object[serializers.length];
|
||||
invokeMethod.args = args;
|
||||
|
||||
for (int i = 0, n = args.length; i < n; i++) {
|
||||
Serializer<?> serializer = serializers[i];
|
||||
if (serializer != null) {
|
||||
|
@ -1,76 +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.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* Uses the "single writer principle" for fast access
|
||||
*/
|
||||
public
|
||||
class OverriddenMethods {
|
||||
// not concurrent because they are setup during system initialization
|
||||
private volatile IdentityMap<Class<?>, Class<?>> overriddenMethods = new IdentityMap<Class<?>, Class<?>>();
|
||||
private volatile IdentityMap<Class<?>, Class<?>> overriddenReverseMethods = new IdentityMap<Class<?>, Class<?>>();
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
|
||||
IdentityMap.class,
|
||||
"overriddenMethods");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<OverriddenMethods, IdentityMap> overriddenReverseMethodsREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(OverriddenMethods.class,
|
||||
IdentityMap.class,
|
||||
"overriddenReverseMethods");
|
||||
|
||||
private static final OverriddenMethods INSTANCE = new OverriddenMethods();
|
||||
public static synchronized OverriddenMethods INSTANCE() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private
|
||||
OverriddenMethods() {
|
||||
}
|
||||
|
||||
/**
|
||||
* access a snapshot of the subscriptions (single-writer-principle)
|
||||
*/
|
||||
public
|
||||
Class<?> get(final Class<?> type) {
|
||||
//noinspection unchecked
|
||||
final IdentityMap<Class<?>, Class<?>> identityMap = overriddenMethodsREF.get(this);
|
||||
return identityMap.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* access a snapshot of the subscriptions (single-writer-principle)
|
||||
*/
|
||||
public
|
||||
Class<?> getReverse(final Class<?> type) {
|
||||
//noinspection unchecked
|
||||
final IdentityMap<Class<?>, Class<?>> identityMap = overriddenReverseMethodsREF.get(this);
|
||||
return identityMap.get(type);
|
||||
}
|
||||
|
||||
// synchronized to make sure only one writer at a time
|
||||
public synchronized
|
||||
void set(final Class<?> ifaceClass, final Class<?> implClass) {
|
||||
this.overriddenMethods.put(ifaceClass, implClass);
|
||||
this.overriddenReverseMethods.put(implClass, ifaceClass);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
* Copyright 2017 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -13,12 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection;
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
class ObjectRegistrationLatch {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Object remoteObject;
|
||||
boolean hasError = false;
|
||||
/**
|
||||
* Callback for creating remote RMI classes
|
||||
*/
|
||||
public
|
||||
interface RemoteObjectCallback<Iface> {
|
||||
/**
|
||||
* @param remoteObject the remote object (as a proxy object) or null if there was an error
|
||||
*/
|
||||
void created(Iface remoteObject);
|
||||
}
|
@ -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 dorkbox.network.connection.KryoExtra;
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,11 @@
|
||||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
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
|
||||
@ -32,4 +36,4 @@ import java.lang.annotation.*;
|
||||
@Inherited
|
||||
@Target(value = {ElementType.TYPE, ElementType.FIELD})
|
||||
public
|
||||
@interface RMI {}
|
||||
@interface Rmi {}
|
@ -34,14 +34,6 @@
|
||||
*/
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import com.esotericsoftware.kryo.util.IntMap;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.util.collections.ObjectIntMap;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
@ -51,13 +43,24 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.esotericsoftware.kryo.util.IntMap;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.util.RmiSerializationManager;
|
||||
import dorkbox.util.collections.ObjectIntMap;
|
||||
|
||||
/**
|
||||
* Allows methods on objects to be invoked remotely over TCP, UDP, UDT, or LOCAL. Local connections ignore TCP/UDP/UDT requests, and perform
|
||||
* object transformation (because there is no serialization occurring) using a series of weak hashmaps.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* Objects are {@link dorkbox.network.util.RMISerializationManager#registerRemote(Class, Class)}, and endpoint connections can then {@link
|
||||
* Connection#createProxyObject(Class)} for the registered objects.
|
||||
* Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link
|
||||
* Connection#getRemoteObject(Class)} 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
|
||||
* is not {@link RemoteObject#setAsync(boolean) ignored}, an extra byte is written. If the type of a parameter is not final (note that
|
||||
@ -409,7 +412,7 @@ class RmiBridge {
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning. This is an advanced method. You should probably be using {@link Connection#createProxyObject(Class)}.
|
||||
* Warning. This is an advanced method. You should probably be using {@link Connection#getRemoteObject(Class, RemoteObjectCallback)}
|
||||
* <p>
|
||||
* <p>
|
||||
* Returns a proxy object that implements the specified interfaces. Methods invoked on the proxy object will be invoked remotely on the
|
||||
@ -443,6 +446,6 @@ class RmiBridge {
|
||||
|
||||
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
|
||||
temp,
|
||||
new RemoteObjectInvocationHandler(connection, objectID));
|
||||
new RmiProxyHandler(connection, objectID));
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +35,6 @@
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.util.RMISerializationManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
@ -49,11 +43,18 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.util.RmiSerializationManager;
|
||||
|
||||
/**
|
||||
* Handles network communication when methods are invoked on a proxy.
|
||||
*/
|
||||
class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RemoteObjectInvocationHandler.class);
|
||||
class RmiProxyHandler implements InvocationHandler {
|
||||
private final Logger logger;
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final Condition responseCondition = this.lock.newCondition();
|
||||
@ -80,12 +81,16 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
private Byte lastResponseID;
|
||||
private byte nextResponseId = (byte) 1;
|
||||
|
||||
RemoteObjectInvocationHandler(final Connection connection, final int objectID) {
|
||||
RmiProxyHandler(final Connection connection, final int objectID) {
|
||||
super();
|
||||
|
||||
this.connection = connection;
|
||||
this.objectID = objectID;
|
||||
this.proxyString = "<proxy #" + objectID + ">";
|
||||
|
||||
logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
|
||||
|
||||
|
||||
this.responseListener = new RemoteInvocationResponse<Connection>() {
|
||||
@Override
|
||||
public
|
||||
@ -99,23 +104,20 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
byte responseID = invokeMethodResult.responseID;
|
||||
|
||||
if (invokeMethodResult.objectID != objectID) {
|
||||
// System.err.println("FAILED: " + responseID);
|
||||
// logger.trace("{} FAILED to received data: {} with id ({})", connection, invokeMethodResult.result, invokeMethodResult.responseID);
|
||||
return;
|
||||
}
|
||||
|
||||
// logger.trace("{} received data: {} with id ({})", connection, invokeMethodResult.result, invokeMethodResult.responseID);
|
||||
synchronized (this) {
|
||||
if (RemoteObjectInvocationHandler.this.pendingResponses[responseID]) {
|
||||
RemoteObjectInvocationHandler.this.responseTable[responseID] = invokeMethodResult;
|
||||
if (RmiProxyHandler.this.pendingResponses[responseID]) {
|
||||
RmiProxyHandler.this.responseTable[responseID] = invokeMethodResult;
|
||||
}
|
||||
}
|
||||
|
||||
RemoteObjectInvocationHandler.this.lock.lock();
|
||||
RmiProxyHandler.this.lock.lock();
|
||||
try {
|
||||
RemoteObjectInvocationHandler.this.responseCondition.signalAll();
|
||||
RmiProxyHandler.this.responseCondition.signalAll();
|
||||
} finally {
|
||||
RemoteObjectInvocationHandler.this.lock.unlock();
|
||||
RmiProxyHandler.this.lock.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -201,10 +203,8 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
return proxyString;
|
||||
}
|
||||
|
||||
final Logger logger1 = RemoteObjectInvocationHandler.logger;
|
||||
|
||||
EndPoint endPoint = this.connection.getEndPoint();
|
||||
final RMISerializationManager serializationManager = endPoint.getSerialization();
|
||||
final RmiSerializationManager serializationManager = endPoint.getSerialization();
|
||||
|
||||
InvokeMethod invokeMethod = new InvokeMethod();
|
||||
invokeMethod.objectID = this.objectID;
|
||||
@ -212,7 +212,7 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
|
||||
|
||||
// which method do we access?
|
||||
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass());
|
||||
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass(), invokeMethod.objectID);
|
||||
|
||||
for (int i = 0, n = cachedMethods.length; i < n; i++) {
|
||||
CachedMethod cachedMethod = cachedMethods[i];
|
||||
@ -245,13 +245,15 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
|
||||
if (invokeMethod.cachedMethod == null) {
|
||||
String msg = "Method not found: " + method;
|
||||
logger1.error(msg);
|
||||
logger.error(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
byte responseID = (byte) 0;
|
||||
// An invocation doesn't need a response is if it's async and no return values or exceptions are wanted back.
|
||||
// An invocation doesn't need a response is if it's
|
||||
// ASYNC and no return values or exceptions are wanted back
|
||||
Class<?> returnType = method.getReturnType();
|
||||
boolean ignoreResponse = this.isAsync && !(this.transmitReturnValue || this.transmitExceptions);
|
||||
if (ignoreResponse) {
|
||||
invokeMethod.responseData = (byte) 0; // 0 means do not respond.
|
||||
@ -276,6 +278,8 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
invokeMethod.responseData = responseData;
|
||||
}
|
||||
|
||||
this.lastResponseID = (byte) (invokeMethod.responseData & RmiBridge.responseIdMask);
|
||||
|
||||
// Sends our invokeMethod to the remote connection, which the RmiBridge listens for
|
||||
if (this.udp) {
|
||||
this.connection.send()
|
||||
@ -293,22 +297,20 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
.flush();
|
||||
}
|
||||
|
||||
if (logger1.isTraceEnabled()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
String argString = "";
|
||||
if (args != null) {
|
||||
argString = Arrays.deepToString(args);
|
||||
argString = argString.substring(1, argString.length() - 1);
|
||||
}
|
||||
logger1.trace(this.connection + " sent: " + method.getDeclaringClass()
|
||||
logger.trace(this.connection + " sent: " + method.getDeclaringClass()
|
||||
.getSimpleName() +
|
||||
"#" + method.getName() + "(" + argString + ")");
|
||||
}
|
||||
|
||||
this.lastResponseID = (byte) (invokeMethod.responseData & RmiBridge.responseIdMask);
|
||||
|
||||
|
||||
// 0 means respond immediately because it's
|
||||
// ASYNC and no return values or exceptions are wanted back
|
||||
if (this.isAsync) {
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType.isPrimitive()) {
|
||||
if (returnType == int.class) {
|
||||
return 0;
|
||||
@ -362,42 +364,19 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
*/
|
||||
private
|
||||
Object waitForResponse(final byte responseID) throws IOException {
|
||||
long endTime = System.currentTimeMillis() + this.timeoutMillis;
|
||||
long remaining = this.timeoutMillis;
|
||||
// if timeout == 0, we wait "forever"
|
||||
long remaining;
|
||||
long endTime;
|
||||
|
||||
if (remaining == 0) {
|
||||
// just wait however log it takes.
|
||||
InvokeMethodResult invokeMethodResult;
|
||||
synchronized (this) {
|
||||
invokeMethodResult = this.responseTable[responseID];
|
||||
if (this.timeoutMillis != 0) {
|
||||
remaining = this.timeoutMillis;
|
||||
endTime = System.currentTimeMillis() + remaining;
|
||||
} else {
|
||||
// not forever, but close enough
|
||||
remaining = Long.MAX_VALUE;
|
||||
endTime = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (invokeMethodResult != null) {
|
||||
this.lastResponseID = null;
|
||||
return invokeMethodResult.result;
|
||||
}
|
||||
else {
|
||||
this.lock.lock();
|
||||
try {
|
||||
this.responseCondition.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread()
|
||||
.interrupt();
|
||||
throw new IOException("Response timed out.", e);
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
invokeMethodResult = this.responseTable[responseID];
|
||||
}
|
||||
if (invokeMethodResult != null) {
|
||||
this.lastResponseID = null;
|
||||
return invokeMethodResult.result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// wait for the specified time
|
||||
while (remaining > 0) {
|
||||
InvokeMethodResult invokeMethodResult;
|
||||
@ -424,7 +403,6 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
|
||||
remaining = endTime - System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
// only get here if we timeout
|
||||
throw new TimeoutException("Response timed out.");
|
||||
@ -457,7 +435,7 @@ class RemoteObjectInvocationHandler implements InvocationHandler {
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RemoteObjectInvocationHandler other = (RemoteObjectInvocationHandler) obj;
|
||||
RmiProxyHandler other = (RmiProxyHandler) obj;
|
||||
return this.objectID == other.objectID;
|
||||
}
|
||||
}
|
@ -21,32 +21,48 @@ package dorkbox.network.rmi;
|
||||
public
|
||||
class RmiRegistration {
|
||||
public Object remoteObject;
|
||||
public String remoteImplementationClass;
|
||||
public boolean hasError;
|
||||
public Class<?> interfaceClass;
|
||||
|
||||
// this is used to get specific, GLOBAL rmi objects (objects that are not bound to a single connection)
|
||||
public int remoteObjectId;
|
||||
|
||||
public
|
||||
public int rmiID;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private
|
||||
RmiRegistration() {
|
||||
hasError = true;
|
||||
// for serialization
|
||||
}
|
||||
|
||||
// When requesting a new remote object to be created.
|
||||
// SENT FROM "client" -> "server"
|
||||
public
|
||||
RmiRegistration(final String remoteImplementationClass) {
|
||||
this.remoteImplementationClass = remoteImplementationClass;
|
||||
hasError = false;
|
||||
RmiRegistration(final Class<?> interfaceClass, final int rmiID) {
|
||||
this.interfaceClass = interfaceClass;
|
||||
this.rmiID = rmiID;
|
||||
}
|
||||
|
||||
// When requesting a new remote object to be created.
|
||||
// SENT FROM "client" -> "server"
|
||||
public
|
||||
RmiRegistration(final Object remoteObject) {
|
||||
this.remoteObject = remoteObject;
|
||||
hasError = false;
|
||||
}
|
||||
|
||||
public
|
||||
RmiRegistration(final int remoteObjectId) {
|
||||
RmiRegistration(final int remoteObjectId, final int rmiID) {
|
||||
this.remoteObjectId = remoteObjectId;
|
||||
hasError = false;
|
||||
this.rmiID = rmiID;
|
||||
}
|
||||
|
||||
|
||||
// When there was an error creating the remote object.
|
||||
// SENT FROM "server" -> "client"
|
||||
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) {
|
||||
this.remoteObject = remoteObject;
|
||||
this.rmiID = rmiID;
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ import java.io.IOException;
|
||||
* RemoteObject#setResponseTimeout(int) response timeout}.
|
||||
*
|
||||
* @author Nathan Sweet <misc@n4te.com>
|
||||
* @see dorkbox.network.connection.Connection#createProxyObject(Class)
|
||||
* @see dorkbox.network.connection.Connection#getRemoteObject(int, RemoteObjectCallback)
|
||||
* @see dorkbox.network.connection.Connection#getRemoteObject(Class, RemoteObjectCallback)
|
||||
*/
|
||||
public
|
||||
class TimeoutException extends IOException {
|
||||
|
@ -15,18 +15,17 @@
|
||||
*/
|
||||
package dorkbox.network.util;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Threads reading/writing, it messes up a single instance. it is possible to use a single kryo with the use of synchronize, however - that
|
||||
* defeats the point of multi-threaded
|
||||
*/
|
||||
public
|
||||
interface CryptoSerializationManager extends SerializationManager, RMISerializationManager {
|
||||
interface CryptoSerializationManager extends RmiSerializationManager {
|
||||
|
||||
/**
|
||||
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
|
||||
@ -46,5 +45,4 @@ interface CryptoSerializationManager extends SerializationManager, RMISerializat
|
||||
* should ALWAYS be the length of the expected object!
|
||||
*/
|
||||
Object readWithCrypto(ConnectionImpl connection, ByteBuf buffer, int length) throws IOException;
|
||||
|
||||
}
|
||||
|
@ -1,49 +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.util;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
|
||||
public
|
||||
interface RMISerializationManager {
|
||||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once when the RMI bridge is created.
|
||||
*/
|
||||
void initRmiSerialization();
|
||||
|
||||
/**
|
||||
* @return takes a kryo instance from the pool.
|
||||
*/
|
||||
KryoExtra takeKryo();
|
||||
|
||||
/**
|
||||
* Returns a kryo instance to the pool.
|
||||
*/
|
||||
void returnKryo(KryoExtra object);
|
||||
|
||||
/**
|
||||
* Objects that we want to use RMI with, must be accessed via an interface. This method configures the serialization of an
|
||||
* implementation to be serialized via the defined interface, as a RemoteObject (ie: proxy object). If the implementation class is
|
||||
* ALREADY registered, then it's registration will be overwritten by this one
|
||||
*
|
||||
* @param ifaceClass
|
||||
* The interface used to access the remote object
|
||||
* @param implClass
|
||||
* The implementation class of the interface
|
||||
*/
|
||||
<Iface, Impl extends Iface> void registerRemote(Class<Iface> ifaceClass, Class<Impl> implClass);
|
||||
}
|
149
src/dorkbox/network/util/RmiSerializationManager.java
Normal file
149
src/dorkbox/network/util/RmiSerializationManager.java
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.Serializer;
|
||||
|
||||
import dorkbox.network.connection.KryoExtra;
|
||||
import dorkbox.util.SerializationManager;
|
||||
|
||||
public
|
||||
interface RmiSerializationManager extends SerializationManager {
|
||||
|
||||
/**
|
||||
* Registers the class using the lowest, next available integer ID and the
|
||||
* {@link Kryo#getDefaultSerializer(Class) default serializer}. If the class
|
||||
* is already registered, the existing entry is updated with the new
|
||||
* serializer. Registering a primitive also affects the corresponding
|
||||
* primitive wrapper.
|
||||
* <p/>
|
||||
* Because the ID assigned is affected by the IDs registered before it, the
|
||||
* order classes are registered is important when using this method. The
|
||||
* order must be the same at deserialization as it was for serialization.
|
||||
*/
|
||||
@Override
|
||||
RmiSerializationManager register(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Registers the class using the lowest, next available integer ID and the
|
||||
* specified serializer. If the class is already registered, the existing
|
||||
* entry is updated with the new serializer. Registering a primitive also
|
||||
* affects the corresponding primitive wrapper.
|
||||
* <p/>
|
||||
* Because the ID assigned is affected by the IDs registered before it, the
|
||||
* order classes are registered is important when using this method. The
|
||||
* order must be the same at deserialization as it was for serialization.
|
||||
*/
|
||||
@Override
|
||||
RmiSerializationManager register(Class<?> clazz, Serializer<?> serializer);
|
||||
|
||||
/**
|
||||
* Registers the class using the specified ID and serializer. If the ID is
|
||||
* already in use by the same type, the old entry is overwritten. If the ID
|
||||
* is already in use by a different type, a {@link KryoException} is thrown.
|
||||
* Registering a primitive also affects the corresponding primitive wrapper.
|
||||
* <p/>
|
||||
* IDs must be the same at deserialization as they were for serialization.
|
||||
*
|
||||
* @param id Must be >= 0. Smaller IDs are serialized more efficiently. IDs
|
||||
* 0-8 are used by default for primitive types and String, but
|
||||
* these IDs can be repurposed.
|
||||
*/
|
||||
@Override
|
||||
RmiSerializationManager register(Class<?> clazz, Serializer<?> serializer, int id);
|
||||
|
||||
/**
|
||||
* Necessary to register classes for RMI, only called once when the RMI bridge is created.
|
||||
*
|
||||
* @return true if there are classes that have been registered for RMI
|
||||
*/
|
||||
boolean initRmiSerialization();
|
||||
|
||||
/**
|
||||
* @return takes a kryo instance from the pool.
|
||||
*/
|
||||
KryoExtra takeKryo();
|
||||
|
||||
/**
|
||||
* Returns a kryo instance to the pool.
|
||||
*/
|
||||
void returnKryo(KryoExtra kryo);
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
*
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
Class<?> getRmiIface(int objectId);
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
|
||||
*
|
||||
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
|
||||
*
|
||||
* @return the implementation for the interface, or null
|
||||
*/
|
||||
Class<?> getRmiImpl(int objectId);
|
||||
|
||||
/**
|
||||
* Gets the RMI implementation based on the specified interface
|
||||
*
|
||||
* @return the corresponding implementation
|
||||
*/
|
||||
Class<?> getRmiImpl(Class<?> iface);
|
||||
|
||||
/**
|
||||
* Gets the RMI interface based on the specified implementation
|
||||
*
|
||||
* @return the corresponding interface
|
||||
*/
|
||||
Class<?> getRmiIface(Class<?> implementation);
|
||||
|
||||
/**
|
||||
* Enable remote method invocation (RMI) for this connection. 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. 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);
|
||||
|
||||
/**
|
||||
* Objects that we want to use RMI with, must be accessed via an interface. This method configures the serialization of an
|
||||
* implementation to be serialized via the defined interface, as a RemoteObject (ie: proxy object). If the implementation class is
|
||||
* ALREADY registered, then it's registration will be overwritten by this one
|
||||
*
|
||||
* @param ifaceClass The interface used to access the remote object
|
||||
* @param implClass The implementation class of the interface
|
||||
*/
|
||||
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass);
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* NOTE: You must have ALREADY registered the implementation class. This method just enables the proxy magic.
|
||||
* This is for the "server" side, where "server" means the connection side where the implementation is used.
|
||||
*
|
||||
* @param ifaceClass The interface used to access the remote object
|
||||
* @param implClass The implementation class of the interface
|
||||
*/
|
||||
// RmiSerializationManager rmiImplementation();
|
||||
}
|
@ -29,10 +29,10 @@ import org.junit.Test;
|
||||
|
||||
import dorkbox.network.PingPongTest.TYPE;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -56,7 +56,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
@ -67,7 +67,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.UDP);
|
||||
@ -78,7 +78,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.UDT);
|
||||
@ -164,7 +164,7 @@ public class ChunkedDataIdleTest extends BaseTest {
|
||||
data.Booleans = new Boolean[] {true, false};
|
||||
}
|
||||
|
||||
private void register (CryptoSerializationManager manager) {
|
||||
private void register (SerializationManager manager) {
|
||||
manager.register(int[].class);
|
||||
manager.register(short[].class);
|
||||
manager.register(float[].class);
|
||||
|
@ -27,9 +27,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -44,7 +44,7 @@ class ClientSendTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
@ -87,7 +87,7 @@ class ClientSendTest extends BaseTest {
|
||||
}
|
||||
|
||||
private static
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(SerializationManager manager) {
|
||||
manager.register(AMessage.class);
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,10 @@ import java.util.TimerTask;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -43,7 +43,7 @@ class ConnectionTest extends BaseTest {
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.localChannelName = EndPoint.LOCAL_CHANNEL;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
@ -59,7 +59,7 @@ class ConnectionTest extends BaseTest {
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
@ -78,7 +78,7 @@ class ConnectionTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
@ -97,7 +97,7 @@ class ConnectionTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
@ -117,7 +117,7 @@ class ConnectionTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
startServer(configuration);
|
||||
@ -194,7 +194,7 @@ class ConnectionTest extends BaseTest {
|
||||
}
|
||||
|
||||
private
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(SerializationManager manager) {
|
||||
manager.register(BMessage.class);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ import org.junit.Test;
|
||||
|
||||
import dorkbox.network.PingPongTest.TYPE;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.idle.IdleBridge;
|
||||
import dorkbox.network.connection.idle.IdleListener;
|
||||
@ -38,7 +38,7 @@ import dorkbox.network.connection.idle.IdleListenerTCP;
|
||||
import dorkbox.network.connection.idle.IdleListenerUDP;
|
||||
import dorkbox.network.connection.idle.IdleListenerUDT;
|
||||
import dorkbox.network.connection.idle.InputStreamSender;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -63,7 +63,7 @@ class IdleTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT(false, false);
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT(false, false);
|
||||
|
||||
streamSpecificType(largeDataSize, configuration, ConnectionType.TCP);
|
||||
|
||||
@ -73,7 +73,7 @@ class IdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT(false, false);
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT(false, false);
|
||||
|
||||
streamSpecificType(largeDataSize, configuration, ConnectionType.UDP);
|
||||
|
||||
@ -83,7 +83,7 @@ class IdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT(false, false);
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT(false, false);
|
||||
|
||||
streamSpecificType(largeDataSize, configuration, ConnectionType.UDT);
|
||||
}
|
||||
@ -102,7 +102,7 @@ class IdleTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
@ -113,7 +113,7 @@ class IdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
@ -124,7 +124,7 @@ class IdleTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
sendObject(mainData, configuration, ConnectionType.TCP);
|
||||
@ -316,7 +316,7 @@ class IdleTest extends BaseTest {
|
||||
}
|
||||
|
||||
private static
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(SerializationManager manager) {
|
||||
manager.register(int[].class);
|
||||
manager.register(short[].class);
|
||||
manager.register(float[].class);
|
||||
|
@ -28,9 +28,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -51,7 +51,7 @@ class LargeResizeBufferTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
@ -141,7 +141,7 @@ class LargeResizeBufferTest extends BaseTest {
|
||||
}
|
||||
|
||||
private
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(SerializationManager manager) {
|
||||
manager.register(byte[].class);
|
||||
manager.register(LargeMessage.class);
|
||||
}
|
||||
|
@ -19,6 +19,18 @@
|
||||
*/
|
||||
package dorkbox.network;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
@ -27,17 +39,6 @@ import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.network.rmi.RmiBridge;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public
|
||||
class ListenerTest extends BaseTest {
|
||||
|
@ -27,7 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
@ -43,7 +43,7 @@ class MultipleServerTest extends BaseTest {
|
||||
configuration1.tcpPort = tcpPort;
|
||||
configuration1.udpPort = udpPort;
|
||||
configuration1.localChannelName = "chan1";
|
||||
configuration1.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration1.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration1.serialization.register(String[].class);
|
||||
|
||||
Server server1 = new Server(configuration1);
|
||||
@ -68,7 +68,7 @@ class MultipleServerTest extends BaseTest {
|
||||
configuration2.tcpPort = tcpPort + 1;
|
||||
configuration2.udpPort = udpPort + 1;
|
||||
configuration2.localChannelName = "chan2";
|
||||
configuration2.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration2.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration2.serialization.register(String[].class);
|
||||
|
||||
Server server2 = new Server(configuration2);
|
||||
|
@ -31,7 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
@ -61,7 +61,7 @@ class MultipleThreadTest extends BaseTest {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization.register(String[].class);
|
||||
configuration.serialization.register(DataClass.class);
|
||||
|
||||
|
@ -28,10 +28,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -48,7 +48,7 @@ class PingPongLocalTest extends BaseTest {
|
||||
populateData(dataLOCAL);
|
||||
|
||||
Configuration configuration = Configuration.localOnly();
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
|
||||
@ -169,7 +169,7 @@ class PingPongLocalTest extends BaseTest {
|
||||
data.Booleans = new Boolean[] {true,false};
|
||||
}
|
||||
|
||||
private void register(CryptoSerializationManager manager) {
|
||||
private void register(SerializationManager manager) {
|
||||
manager.register(int[].class);
|
||||
manager.register(short[].class);
|
||||
manager.register(float[].class);
|
||||
|
@ -29,11 +29,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.SerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -62,7 +62,7 @@ class PingPongTest extends BaseTest {
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.udtPort = udtPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
|
||||
@ -290,7 +290,7 @@ class PingPongTest extends BaseTest {
|
||||
}
|
||||
|
||||
private
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(SerializationManager manager) {
|
||||
manager.register(int[].class);
|
||||
manager.register(short[].class);
|
||||
manager.register(float[].class);
|
||||
|
@ -29,8 +29,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
@ -54,7 +54,7 @@ class UnregisteredClassTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT(false, false);
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT(false, false);
|
||||
|
||||
|
||||
System.err.println("Running test " + this.tries + " times, please wait for it to finish.");
|
||||
|
11
test/dorkbox/network/rmi/MessageWithTestCow.java
Normal file
11
test/dorkbox/network/rmi/MessageWithTestCow.java
Normal file
@ -0,0 +1,11 @@
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class MessageWithTestCow implements RmiMessages {
|
||||
public int number;
|
||||
public String text;
|
||||
public TestCow testCow;
|
||||
}
|
@ -40,7 +40,6 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -50,9 +49,8 @@ import dorkbox.network.Configuration;
|
||||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
@ -62,23 +60,14 @@ class RmiGlobalTest extends BaseTest {
|
||||
private int CLIENT_GLOBAL_OBJECT_ID = 0;
|
||||
private int SERVER_GLOBAL_OBJECT_ID = 0;
|
||||
|
||||
private final TestObject globalRemoteServerObject = new TestObjectImpl();
|
||||
private final TestObject globalRemoteClientObject = new TestObjectImpl();
|
||||
private final TestCow globalRemoteServerObject = new TestCowImpl();
|
||||
private final TestCow globalRemoteClientObject = new TestCowImpl();
|
||||
|
||||
private static
|
||||
void runTest(final Connection connection, final Object remoteObject, final int remoteObjectID) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
TestObject test = connection.getProxyObject(remoteObjectID);
|
||||
|
||||
void runTest(final Connection connection, final TestCow rObject, final TestCow test, final int remoteObjectID) {
|
||||
System.err.println("Starting test for: " + remoteObjectID);
|
||||
|
||||
|
||||
//TestObject test = connection.getRemoteObject(id, TestObject.class);
|
||||
assertEquals(remoteObject.hashCode(), test.hashCode());
|
||||
assertEquals(rObject.hashCode(), test.hashCode());
|
||||
RemoteObject remoteObject = (RemoteObject) test;
|
||||
|
||||
// Default behavior. RMI is transparent, method calls behave like normal
|
||||
@ -180,30 +169,21 @@ class RmiGlobalTest extends BaseTest {
|
||||
|
||||
|
||||
// Test sending a reference to a remote object.
|
||||
MessageWithTestObject m = new MessageWithTestObject();
|
||||
MessageWithTestCow m = new MessageWithTestCow();
|
||||
m.number = 678;
|
||||
m.text = "sometext";
|
||||
m.testObject = test;
|
||||
m.testCow = test;
|
||||
connection.send()
|
||||
.TCP(m)
|
||||
.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(dorkbox.network.util.CryptoSerializationManager manager) {
|
||||
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
|
||||
|
||||
manager.registerRemote(TestObject.class, TestObjectImpl.class);
|
||||
manager.register(MessageWithTestObject.class);
|
||||
|
||||
manager.register(MessageWithTestCow.class);
|
||||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@ -215,10 +195,15 @@ class RmiGlobalTest extends BaseTest {
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.rmiEnabled = true;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
register(configuration.serialization);
|
||||
|
||||
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
@ -234,19 +219,42 @@ class RmiGlobalTest extends BaseTest {
|
||||
@Override
|
||||
public
|
||||
void connected(final Connection connection) {
|
||||
RmiGlobalTest.runTest(connection, globalRemoteClientObject, CLIENT_GLOBAL_OBJECT_ID);
|
||||
try {
|
||||
connection.getRemoteObject(CLIENT_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Server -> Client");
|
||||
runTest(connection, globalRemoteClientObject, remoteObject, CLIENT_GLOBAL_OBJECT_ID);
|
||||
System.err.println("Done with test for: Server -> Client");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Client/Server Finished!");
|
||||
assertEquals(SERVER_GLOBAL_OBJECT_ID, id);
|
||||
|
||||
System.err.println("Finished test for: Client -> Server");
|
||||
|
||||
stopEndPoints(2000);
|
||||
}
|
||||
@ -254,27 +262,65 @@ class RmiGlobalTest extends BaseTest {
|
||||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.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);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
|
||||
// register this object as a global object that the server will get
|
||||
CLIENT_GLOBAL_OBJECT_ID = client.createGlobalObject(globalRemoteClientObject);
|
||||
|
||||
addEndPoint(client);
|
||||
|
||||
client.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Server -> Client");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Server/Client Finished!");
|
||||
assertEquals(CLIENT_GLOBAL_OBJECT_ID, id);
|
||||
|
||||
System.err.println("Finished test for: Server -> Client");
|
||||
|
||||
// normally this is in the 'connected', but we do it here, so that it's more linear and easier to debug
|
||||
runTest(connection, globalRemoteServerObject, SERVER_GLOBAL_OBJECT_ID);
|
||||
try {
|
||||
connection.getRemoteObject(SERVER_GLOBAL_OBJECT_ID, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Client -> Server");
|
||||
runTest(connection, globalRemoteServerObject, remoteObject, SERVER_GLOBAL_OBJECT_ID);
|
||||
System.err.println("Done with test for: Client -> Server");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -282,23 +328,8 @@ class RmiGlobalTest extends BaseTest {
|
||||
waitForThreads();
|
||||
}
|
||||
|
||||
public
|
||||
interface TestObject extends Serializable {
|
||||
void throwException();
|
||||
|
||||
void moo();
|
||||
|
||||
void moo(String value);
|
||||
|
||||
void moo(String value, long delay);
|
||||
|
||||
int id();
|
||||
|
||||
float slow();
|
||||
}
|
||||
|
||||
|
||||
public static class ConnectionAware {
|
||||
private static
|
||||
class ConnectionAware {
|
||||
private
|
||||
ConnectionImpl connection;
|
||||
|
||||
@ -313,14 +344,15 @@ class RmiGlobalTest extends BaseTest {
|
||||
}
|
||||
}
|
||||
|
||||
public static
|
||||
class TestObjectImpl extends ConnectionAware implements TestObject {
|
||||
|
||||
private static
|
||||
class TestCowImpl extends ConnectionAware implements TestCow {
|
||||
public long value = System.currentTimeMillis();
|
||||
public int moos;
|
||||
private final int id = 1;
|
||||
|
||||
public
|
||||
TestObjectImpl() {
|
||||
TestCowImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -373,12 +405,4 @@ class RmiGlobalTest extends BaseTest {
|
||||
return 123.0F;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
class MessageWithTestObject implements RmiMessages {
|
||||
public int number;
|
||||
public String text;
|
||||
public TestObject testObject;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import dorkbox.network.Client;
|
||||
import dorkbox.network.Configuration;
|
||||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
@ -71,13 +71,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.rmiEnabled = true;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
|
||||
configuration.serialization.registerRemote(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRemote(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
@ -105,6 +101,14 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
|
||||
Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
@ -114,24 +118,26 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
@Override
|
||||
public
|
||||
void connected(final Connection connection) {
|
||||
new Thread(new Runnable() {
|
||||
try {
|
||||
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
|
||||
connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestObject remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
remoteObject.setOther(43.21f);
|
||||
|
||||
|
||||
TestObject test = null;
|
||||
try {
|
||||
test = connection.createProxyObject(TestObjectImpl.class);
|
||||
|
||||
test.setOther(43.21f);
|
||||
// Normal remote method call.
|
||||
assertEquals(43.21f, test.other(), .0001f);
|
||||
assertEquals(43.21f, remoteObject.other(), .0001f);
|
||||
|
||||
// Make a remote method call that returns another remote proxy object.
|
||||
// the "test" object exists in the REMOTE side, as does the "OtherObject" that is created.
|
||||
// here we have a proxy to both of them.
|
||||
OtherObject otherObject = test.getOtherObject();
|
||||
OtherObject otherObject = remoteObject.getOtherObject();
|
||||
|
||||
// Normal remote method call on the second object.
|
||||
otherObject.setValue(12.34f);
|
||||
@ -145,13 +151,16 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
connection.send()
|
||||
.TCP(otherObject)
|
||||
.flush();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
client.connect(5000);
|
||||
@ -159,7 +168,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
waitForThreads();
|
||||
}
|
||||
|
||||
public
|
||||
private
|
||||
interface TestObject {
|
||||
void setOther(float aFloat);
|
||||
|
||||
@ -169,7 +178,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
private
|
||||
interface OtherObject {
|
||||
void setValue(float aFloat);
|
||||
float value();
|
||||
@ -179,12 +188,12 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
private static final AtomicInteger idCounter = new AtomicInteger();
|
||||
|
||||
|
||||
public static
|
||||
private static
|
||||
class TestObjectImpl implements TestObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
|
||||
@RMI
|
||||
@Rmi
|
||||
private final OtherObject otherObject = new OtherObjectImpl();
|
||||
private float aFloat;
|
||||
|
||||
@ -230,7 +239,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
private static
|
||||
class OtherObjectImpl implements OtherObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
|
@ -47,7 +47,7 @@ import dorkbox.network.Client;
|
||||
import dorkbox.network.Configuration;
|
||||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
@ -68,12 +68,11 @@ class RmiSendObjectTest extends BaseTest {
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.rmiEnabled = true;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
||||
configuration.serialization.register(TestObject.class);
|
||||
configuration.serialization.registerRemote(TestObject.class, TestObjectImpl.class);
|
||||
configuration.serialization.registerRemote(OtherObject.class, OtherObjectImpl.class);
|
||||
|
||||
|
||||
Server server = new Server(configuration);
|
||||
@ -100,6 +99,15 @@ class RmiSendObjectTest extends BaseTest {
|
||||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization.registerRmiInterface(TestObject.class);
|
||||
configuration.serialization.registerRmiInterface(OtherObject.class);
|
||||
|
||||
|
||||
Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
@ -109,20 +117,24 @@ class RmiSendObjectTest extends BaseTest {
|
||||
@Override
|
||||
public
|
||||
void connected(final Connection connection) {
|
||||
new Thread(new Runnable() {
|
||||
try {
|
||||
connection.getRemoteObject(TestObject.class, new RemoteObjectCallback<TestObject>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestObject remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
TestObject test = null;
|
||||
try {
|
||||
test = connection.createProxyObject(TestObjectImpl.class);
|
||||
remoteObject.setOther(43.21f);
|
||||
|
||||
test.setOther(43.21f);
|
||||
// Normal remote method call.
|
||||
assertEquals(43.21f, test.other(), 0.0001F);
|
||||
assertEquals(43.21f, remoteObject.other(), 0.0001F);
|
||||
|
||||
// Make a remote method call that returns another remote proxy object.
|
||||
OtherObject otherObject = test.getOtherObject();
|
||||
OtherObject otherObject = remoteObject.getOtherObject();
|
||||
|
||||
// Normal remote method call on the second object.
|
||||
otherObject.setValue(12.34f);
|
||||
float value = otherObject.value();
|
||||
@ -133,13 +145,15 @@ class RmiSendObjectTest extends BaseTest {
|
||||
connection.send()
|
||||
.TCP(otherObject)
|
||||
.flush();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
client.connect(5000);
|
||||
@ -147,7 +161,7 @@ class RmiSendObjectTest extends BaseTest {
|
||||
waitForThreads();
|
||||
}
|
||||
|
||||
public
|
||||
private
|
||||
interface TestObject {
|
||||
void setOther(float aFloat);
|
||||
float other();
|
||||
@ -155,7 +169,7 @@ class RmiSendObjectTest extends BaseTest {
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
private
|
||||
interface OtherObject {
|
||||
void setValue(float aFloat);
|
||||
float value();
|
||||
@ -164,13 +178,12 @@ class RmiSendObjectTest extends BaseTest {
|
||||
|
||||
private static final AtomicInteger idCounter = new AtomicInteger();
|
||||
|
||||
|
||||
public static
|
||||
private static
|
||||
class TestObjectImpl implements TestObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
|
||||
@RMI
|
||||
@Rmi
|
||||
private final OtherObject otherObject = new OtherObjectImpl();
|
||||
private float aFloat;
|
||||
|
||||
@ -201,7 +214,7 @@ class RmiSendObjectTest extends BaseTest {
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
private static
|
||||
class OtherObjectImpl implements OtherObject {
|
||||
@IgnoreSerialization
|
||||
private final int ID = idCounter.getAndIncrement();
|
||||
|
@ -40,7 +40,6 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@ -49,32 +48,23 @@ import dorkbox.network.Client;
|
||||
import dorkbox.network.Configuration;
|
||||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.KryoCryptoSerializationManager;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.connection.Listener;
|
||||
import dorkbox.network.connection.ListenerBridge;
|
||||
import dorkbox.network.util.CryptoSerializationManager;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
public
|
||||
class RmiTest extends BaseTest {
|
||||
|
||||
private static
|
||||
void runTest(final Connection connection, final int remoteObjectID) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
TestObject test = connection.createProxyObject(TestObjectImpl.class);
|
||||
|
||||
//TestObject test = connection.getRemoteObject(id, TestObject.class);
|
||||
public static
|
||||
void runTests(final Connection connection, final TestCow test, final int remoteObjectID) {
|
||||
RemoteObject remoteObject = (RemoteObject) test;
|
||||
|
||||
// Default behavior. RMI is transparent, method calls behave like normal
|
||||
// (return values and exceptions are returned, call is synchronous)
|
||||
System.err.println("hashCode: " + test.hashCode());
|
||||
System.err.println("toString: " + test);
|
||||
System.err.println("toString: " + test.toString());
|
||||
|
||||
// see what the "remote" toString() method is
|
||||
final String s = remoteObject.toString();
|
||||
@ -93,6 +83,7 @@ class RmiTest extends BaseTest {
|
||||
remoteObject.setTransmitExceptions(false);
|
||||
test.moo("Meow");
|
||||
assertEquals(0, test.id());
|
||||
|
||||
remoteObject.setAsync(false);
|
||||
remoteObject.setTransmitReturnValue(true);
|
||||
remoteObject.setTransmitExceptions(true);
|
||||
@ -168,29 +159,20 @@ class RmiTest extends BaseTest {
|
||||
|
||||
|
||||
// Test sending a reference to a remote object.
|
||||
MessageWithTestObject m = new MessageWithTestObject();
|
||||
MessageWithTestCow m = new MessageWithTestCow();
|
||||
m.number = 678;
|
||||
m.text = "sometext";
|
||||
m.testObject = test;
|
||||
m.testCow = test;
|
||||
connection.send()
|
||||
.TCP(m)
|
||||
.flush();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public static
|
||||
void register(CryptoSerializationManager manager) {
|
||||
void register(dorkbox.network.util.CryptoSerializationManager manager) {
|
||||
manager.register(Object.class); // Needed for Object#toString, hashCode, etc.
|
||||
|
||||
manager.registerRemote(TestObject.class, TestObjectImpl.class);
|
||||
manager.register(MessageWithTestObject.class);
|
||||
|
||||
manager.register(MessageWithTestCow.class);
|
||||
manager.register(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@ -202,10 +184,15 @@ class RmiTest extends BaseTest {
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.rmiEnabled = true;
|
||||
configuration.serialization = KryoCryptoSerializationManager.DEFAULT();
|
||||
configuration.serialization = CryptoSerializationManager.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);
|
||||
|
||||
|
||||
final Server server = new Server(configuration);
|
||||
server.setIdleTimeout(0);
|
||||
@ -214,50 +201,113 @@ class RmiTest extends BaseTest {
|
||||
server.bind(false);
|
||||
|
||||
final ListenerBridge listeners = server.listeners();
|
||||
listeners.add(new Listener.OnConnected<Connection>() {
|
||||
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void connected(final Connection connection) {
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Finished test for: Client -> Server");
|
||||
|
||||
|
||||
System.err.println("Starting test for: Server -> Client");
|
||||
RmiTest.runTest(connection, 1);
|
||||
|
||||
// normally this is in the 'connected', but we do it here, so that it's more linear and easier to debug
|
||||
try {
|
||||
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
|
||||
connection.getRemoteObject(TestCow.class, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Server -> Client");
|
||||
runTests(connection, remoteObject, 2);
|
||||
System.err.println("Done with test for: Server -> Client");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
listeners.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
final int id = object.id();
|
||||
assertEquals(2, id);
|
||||
System.err.println("Client -> Server Finished!");
|
||||
|
||||
stopEndPoints(2000);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ----
|
||||
configuration = new Configuration();
|
||||
configuration.tcpPort = tcpPort;
|
||||
configuration.udpPort = udpPort;
|
||||
configuration.host = host;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.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);
|
||||
|
||||
|
||||
final Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
addEndPoint(client);
|
||||
|
||||
client.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestObject>() {
|
||||
|
||||
client.listeners().add(new Listener.OnConnected<Connection>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestObject m) {
|
||||
TestObject object = m.testObject;
|
||||
final int id = object.id();
|
||||
assertEquals(1, id);
|
||||
System.err.println("Server -> Client Finished!");
|
||||
|
||||
void connected(final Connection connection) {
|
||||
System.err.println("Starting test for: Client -> Server");
|
||||
// normally this is in the 'connected', but we do it here, so that it's more linear and easier to debug
|
||||
runTest(connection, 2);
|
||||
|
||||
try {
|
||||
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
|
||||
connection.getRemoteObject(TestCow.class, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestCow remoteObject) {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
System.err.println("Running test for: Client -> Server");
|
||||
runTests(connection, remoteObject, 1);
|
||||
System.err.println("Done with test for: Client -> Server");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.listeners()
|
||||
.add(new Listener.OnMessageReceived<Connection, MessageWithTestCow>() {
|
||||
@Override
|
||||
public
|
||||
void received(Connection connection, MessageWithTestCow m) {
|
||||
System.err.println("Received finish signal for test for: Client -> Server");
|
||||
|
||||
TestCow object = m.testCow;
|
||||
final int id = object.id();
|
||||
assertEquals(2, id);
|
||||
System.err.println("Finished test for: Client -> Server");
|
||||
|
||||
stopEndPoints(2000);
|
||||
}
|
||||
});
|
||||
|
||||
@ -265,120 +315,4 @@ class RmiTest extends BaseTest {
|
||||
|
||||
waitForThreads();
|
||||
}
|
||||
|
||||
public
|
||||
interface TestObject {
|
||||
void throwException();
|
||||
|
||||
void moo();
|
||||
|
||||
void moo(String value);
|
||||
|
||||
void moo(String value, long delay);
|
||||
|
||||
int id();
|
||||
|
||||
float slow();
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
class TestObjectImpl implements TestObject {
|
||||
// has to start at 1, because UDP/UDT method invocations ignore return values
|
||||
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
|
||||
|
||||
public long value = System.currentTimeMillis();
|
||||
public int moos;
|
||||
private final int id = ID_COUNTER.getAndIncrement();
|
||||
|
||||
public
|
||||
TestObjectImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void throwException() {
|
||||
throw new UnsupportedOperationException("Why would I do that?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo() {
|
||||
this.moos++;
|
||||
System.out.println("Moo!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value) {
|
||||
this.moos += 2;
|
||||
System.out.println("Moo: " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value, long delay) {
|
||||
this.moos += 4;
|
||||
System.out.println("Moo: " + value + " (" + delay + ")");
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
float slow() {
|
||||
System.out.println("Slowdown!!");
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 123.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TestObjectImpl that = (TestObjectImpl) o;
|
||||
|
||||
return id == that.id;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return "Tada! This is a remote object!";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
class MessageWithTestObject implements RmiMessages {
|
||||
public int number;
|
||||
public String text;
|
||||
public TestObject testObject;
|
||||
}
|
||||
}
|
||||
|
19
test/dorkbox/network/rmi/TestCow.java
Normal file
19
test/dorkbox/network/rmi/TestCow.java
Normal file
@ -0,0 +1,19 @@
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
interface TestCow {
|
||||
void throwException();
|
||||
|
||||
void moo();
|
||||
|
||||
void moo(String value);
|
||||
|
||||
void moo(String value, long delay);
|
||||
|
||||
int id();
|
||||
|
||||
float slow();
|
||||
}
|
98
test/dorkbox/network/rmi/TestCowImpl.java
Normal file
98
test/dorkbox/network/rmi/TestCowImpl.java
Normal file
@ -0,0 +1,98 @@
|
||||
package dorkbox.network.rmi;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class TestCowImpl implements TestCow {
|
||||
// has to start at 1, because UDP/UDT method invocations ignore return values
|
||||
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
|
||||
|
||||
public long value = System.currentTimeMillis();
|
||||
public int moos;
|
||||
private final int id = ID_COUNTER.getAndIncrement();
|
||||
|
||||
public
|
||||
TestCowImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void throwException() {
|
||||
throw new UnsupportedOperationException("Why would I do that?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo() {
|
||||
this.moos++;
|
||||
System.out.println("Moo!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value) {
|
||||
this.moos += 2;
|
||||
System.out.println("Moo: " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void moo(String value, long delay) {
|
||||
this.moos += 4;
|
||||
System.out.println("Moo: " + value + " (" + delay + ")");
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
float slow() {
|
||||
System.out.println("Slowdown!!");
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 123.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TestCowImpl that = (TestCowImpl) o;
|
||||
|
||||
return id == that.id;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return "Tada! This is a remote object!";
|
||||
}
|
||||
}
|
133
test/dorkbox/network/rmi/multiJVM/TestClient.java
Normal file
133
test/dorkbox/network/rmi/multiJVM/TestClient.java
Normal file
@ -0,0 +1,133 @@
|
||||
package dorkbox.network.rmi.multiJVM;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import dorkbox.network.Client;
|
||||
import dorkbox.network.Configuration;
|
||||
import dorkbox.network.connection.Connection;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.rmi.RemoteObjectCallback;
|
||||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class TestClient
|
||||
{
|
||||
public static void setup() {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
|
||||
|
||||
// assume SLF4J is bound to logback in the current environment
|
||||
Logger rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||
LoggerContext context = rootLogger.getLoggerContext();
|
||||
|
||||
JoranConfigurator jc = new JoranConfigurator();
|
||||
jc.setContext(context);
|
||||
context.reset(); // override default configuration
|
||||
|
||||
// rootLogger.setLevel(Level.OFF);
|
||||
|
||||
rootLogger.setLevel(Level.DEBUG);
|
||||
// rootLogger.setLevel(Level.TRACE);
|
||||
// rootLogger.setLevel(Level.ALL);
|
||||
|
||||
|
||||
// we only want error messages
|
||||
Logger nettyLogger = (Logger) LoggerFactory.getLogger("io.netty");
|
||||
nettyLogger.setLevel(Level.ERROR);
|
||||
|
||||
// we only want error messages
|
||||
Logger kryoLogger = (Logger) LoggerFactory.getLogger("com.esotericsoftware");
|
||||
kryoLogger.setLevel(Level.ERROR);
|
||||
|
||||
// we only want error messages
|
||||
Logger barchartLogger = (Logger) LoggerFactory.getLogger("com.barchart");
|
||||
barchartLogger.setLevel(Level.ERROR);
|
||||
|
||||
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
|
||||
encoder.setContext(context);
|
||||
encoder.setPattern("%date{HH:mm:ss.SSS} %-5level [%logger{35}] %msg%n");
|
||||
encoder.start();
|
||||
|
||||
ConsoleAppender<ILoggingEvent> consoleAppender = new ch.qos.logback.core.ConsoleAppender<ILoggingEvent>();
|
||||
|
||||
consoleAppender.setContext(context);
|
||||
consoleAppender.setEncoder(encoder);
|
||||
consoleAppender.start();
|
||||
|
||||
rootLogger.addAppender(consoleAppender);
|
||||
}
|
||||
|
||||
|
||||
public static
|
||||
void main(String[] args) {
|
||||
setup();
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
configuration.udtPort = 2002;
|
||||
configuration.host = "localhost";
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiInterface(TestCow.class);
|
||||
|
||||
|
||||
try {
|
||||
Client client = new Client(configuration);
|
||||
client.setIdleTimeout(0);
|
||||
|
||||
client.listeners()
|
||||
.add(new dorkbox.network.connection.Listener.OnConnected<Connection>() {
|
||||
@Override
|
||||
public
|
||||
void connected(final Connection connection) {
|
||||
System.err.println("Starting test for: Client -> Server");
|
||||
|
||||
try {
|
||||
// if this is called in the dispatch thread, it will block network comms while waiting for a response and it won't work...
|
||||
connection.getRemoteObject(TestCow.class, new RemoteObjectCallback<TestCow>() {
|
||||
@Override
|
||||
public
|
||||
void created(final TestCow remoteObject) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// MUST run on a separate thread because remote object method invocations are blocking
|
||||
RmiTest.runTests(connection, remoteObject, 1);
|
||||
System.err.println("DONE");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.connect(0);
|
||||
|
||||
Thread.sleep(999999999);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
83
test/dorkbox/network/rmi/multiJVM/TestServer.java
Normal file
83
test/dorkbox/network/rmi/multiJVM/TestServer.java
Normal file
@ -0,0 +1,83 @@
|
||||
package dorkbox.network.rmi.multiJVM;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dorkbox.network.Server;
|
||||
import dorkbox.network.connection.CryptoSerializationManager;
|
||||
import dorkbox.network.rmi.RmiTest;
|
||||
import dorkbox.network.rmi.TestCow;
|
||||
import dorkbox.network.rmi.TestCowImpl;
|
||||
import dorkbox.util.exceptions.InitializationException;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class TestServer
|
||||
{
|
||||
public static
|
||||
void main(String[] args) {
|
||||
TestClient.setup();
|
||||
|
||||
dorkbox.network.Configuration configuration = new dorkbox.network.Configuration();
|
||||
configuration.tcpPort = 2000;
|
||||
configuration.udpPort = 2001;
|
||||
configuration.udtPort = 2002;
|
||||
|
||||
configuration.serialization = CryptoSerializationManager.DEFAULT();
|
||||
RmiTest.register(configuration.serialization);
|
||||
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
|
||||
|
||||
Server server = null;
|
||||
try {
|
||||
server = new Server(configuration);
|
||||
} catch (InitializationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
server.setIdleTimeout(0);
|
||||
server.bind(true);
|
||||
|
||||
|
||||
// configuration.host = "localhost";
|
||||
// configuration.serialization.register(TestObjImpl.class);
|
||||
//
|
||||
// Client client = null;
|
||||
// try {
|
||||
// client = new Client(configuration);
|
||||
// } catch (InitializationException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (SecurityException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// client.setIdleTimeout(0);
|
||||
//
|
||||
// client.listeners()
|
||||
// .add(new dorkbox.network.connection.Listener.OnConnected<Connection>() {
|
||||
// @Override
|
||||
// public
|
||||
// void connected(final Connection connection) {
|
||||
// System.err.println("CONNECTED!");
|
||||
//
|
||||
// try {
|
||||
// TestCow object = connection.createProxyObject(TestCow.class);
|
||||
// object.test();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// try {
|
||||
// client.connect(5000);
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}}
|
Loading…
Reference in New Issue
Block a user