Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.refcodes.remoting.RemoteClient Maven / Gradle / Ivy
Go to download
Artifact with proxy functionality for plain remote procedure calls
(RPC), a POJO alternative to RMI.
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.remoting;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.refcodes.component.DigestException;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryTimeout;
import org.refcodes.data.IoTimeout;
import org.refcodes.data.RetryLoopCount;
import org.refcodes.data.SleepLoopTime;
import org.refcodes.exception.TimeoutIOException;
import org.refcodes.exception.VetoException;
import org.refcodes.generator.Generator;
import org.refcodes.generator.UniqueIdGenerator;
import org.refcodes.mixin.BusyAccessor;
import org.refcodes.mixin.Disposable;
import org.refcodes.mixin.Lockable;
/**
* A {@link RemoteClient} promotes subjects to be operated on by a
* {@link RemoteServer}.
*/
public class RemoteClient extends AbstractRemote {
// /////////////////////////////////////////////////////////////////////////
// STATICS:
// /////////////////////////////////////////////////////////////////////////
private static final Logger LOGGER = Logger.getLogger( RemoteClient.class.getName() );
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private InstanceHandler _instanceHandler = new InstanceHandler();
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
/**
* Default constructor of the {@link RemoteClient} will use a default
* {@link ExecutorService}.
*/
public RemoteClient() {}
/**
* Instantiates a new {@link RemoteClient} instance with the given
* {@link ExecutorService} to be used.
*
* @param aExecutorService The {@link ExecutorService} to be used.
*/
public RemoteClient( ExecutorService aExecutorService ) {
super( aExecutorService );
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public void clear() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
signOffAllProxies();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void close() throws IOException {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "CLOSE called on <" + getClass().getName() + ">." );
}
close( null );
}
/**
* {@inheritDoc}
*/
@Override
public boolean isBusy() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isBusy();
}
/**
* Returns true if the provided proxy is contained inside the
* {@link RemoteClient}.
*
* @param aProxy The proxy to be tested if it is contained inside the
* {@link RemoteClient}.
*
* @return True if the given proxy is contained inside the
* {@link RemoteClient}.
*/
public boolean hasProxy( Object aProxy ) {
return getProxyDescriptor( aProxy ) != null;
}
/**
* Returns the proxy which can be cast to the given type. Note that this
* method may throw an {@link AmbiguousProxyException} even if
* {@link #hasProxy(Class)} returns true! The method
* {@link #hasProxy(Class)} returns true in case at least one proxy of the
* given type was found whereas this method only returns a proxy in case
* there is exactly one proxy of the required type is present.
*
* @param the generic type
* @param aType the type
*
* @return The proxy for an instance of the given type.
*
* @throws AmbiguousProxyException Thrown in case a proxy for a given type
* was requested but more than one proxies matched the requested
* type.
* @throws NoSuchProxyException Thrown in case a proxy for a given type was
* requested but not any proxy matched the requested type.
*/
@SuppressWarnings("unchecked")
public T getProxy( Class aType ) throws AmbiguousProxyException, NoSuchProxyException {
final Iterator e = proxies();
Object eProxy;
Object theProxy = null;
while ( e.hasNext() ) {
eProxy = e.next();
if ( aType.isAssignableFrom( eProxy.getClass() ) ) {
if ( theProxy != null ) {
throw new AmbiguousProxyException( "More than one proxy matching the given class \"" + aType.getName() + "\" found; an ambigous state detected; only one proxy must match your type!" );
}
theProxy = eProxy;
}
}
if ( theProxy == null ) {
throw new NoSuchProxyException( "Not one proxy matching the given class \"" + aType.getName() + "\" found; exactly one proxy must match your type!" );
}
return (T) theProxy;
}
/**
* Returns true if there is at least one proxy of the given type.
*
* @param aType the type
*
* @return True in case there is at least one proxy of the given type.
*/
public boolean hasProxy( Class> aType ) {
final Iterator e = proxies();
Object eProxy;
while ( e.hasNext() ) {
eProxy = e.next();
if ( aType.isAssignableFrom( eProxy.getClass() ) ) {
return true;
}
}
return false;
}
/**
* Returns a read-only iterator containing all the proxy objects previously
* being published. Use the sign-off methods in order to remove a published
* proxy object.
*
* @return An iterator containing the published proxy objects.
*/
public Iterator proxies() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
final ArrayList theProxyList = new ArrayList<>( _instanceHandler.size() );
synchronized ( _instanceHandler ) {
final Iterator e = _instanceHandler.proxyDescriptors();
while ( e.hasNext() ) {
theProxyList.add( e.next().getProxy() );
}
}
return theProxyList.iterator();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isEmpty();
}
/**
* Signs off an instance previously published using the
* publishClassDescriptor() method.
*
* @param aProxy An object of type GenericInstanceDescriptor containing the
* information needed to sign-off an instance.
*
* @return True is returned if the instance described by the proxy object
* could be signed off, else false is returned
*
* @throws IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
public boolean signOffProxy( Object aProxy ) throws IOException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
if ( aProxy == null ) {
return false;
}
final ProxyDescriptor theProxyDescriptor = getProxyDescriptor( aProxy );
if ( theProxyDescriptor == null ) {
return false;
}
final String theInstanceId = theProxyDescriptor.getInstanceId();
if ( !isClosed() ) {
if ( theProxyDescriptor.getInstanceId() == null ) {
return false;
}
if ( _instanceHandler.hasSignedOffInstanceId( theInstanceId ) ) {
return false;
}
if ( !_instanceHandler.hasProxyControl( theInstanceId ) ) {
return false;
}
}
ProxyControl theProxyControl = _instanceHandler.getProxyControl( theInstanceId );
// ---------------------------------------------------------------------
// Signal hook:
// ---------------------------------------------------------------------
onProxySignedOff( theProxyDescriptor.getProxy() );
// ---------------------------------------------------------------------
theProxyControl.lock();
waitForActiveSessions( theProxyControl );
synchronized ( _instanceHandler ) {
if ( _instanceHandler.hasSignedOffInstanceId( theInstanceId ) ) {
return false;
}
theProxyControl = _instanceHandler.getProxyControl( theInstanceId );
_instanceHandler.removeProxyDescriptor( theInstanceId );
// <--|
if ( _instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) {
theProxyControl.dispose();
throw new DuplicateInstanceIdRuntimeException( "The instance of the provided object in arguemnt is already in use and being calluted. Sorry - aborting operation !!!" );
}
}
final SignOffProxyMessage theSignOffProxyJob = new SignOffProxyMessage();
theSignOffProxyJob.setInstanceId( theProxyDescriptor.getInstanceId() );
final CancelMethodReplyMessage theCancelMethodReplyJobImpl = new CancelMethodReplyMessage();
theCancelMethodReplyJobImpl.setInstanceId( theInstanceId );
theCancelMethodReplyJobImpl.setHasReply( false );
_instanceHandler.addReplyDescriptor( theCancelMethodReplyJobImpl, theInstanceId );
try {
toReceiver( theSignOffProxyJob );
}
catch ( IOException aException ) {
theProxyControl.dispose();
theProxyControl.unlock();
_instanceHandler.removeMethodReplyDescriptor( theInstanceId );
if ( aException.getCause() instanceof IOException ) {
closeOnException();
}
throw aException;
}
final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
while ( !theCancelMethodReplyJobImpl.hasReply() && theRetryTimeout.hasNextRetry() && isOpened() && !theProxyControl.isUnusable() ) {
// @formatter:off
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while waiting for method reply for <" + WAIT_FOR_REPLY_LOOPS + "> ms; connection status is " + getConnectionStatus() + "." );
}
// @formatter:on
theRetryTimeout.nextRetry( theCancelMethodReplyJobImpl );
}
theProxyControl.dispose();
_instanceHandler.removeMethodReplyDescriptor( theInstanceId );
if ( !theCancelMethodReplyJobImpl.hasReply() ) {
throw new TimeoutIOException( WAIT_FOR_REPLY_TIMEOUT, "While processing the request a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot! Propably lost the connection (you propably should close the connection). Sorry - request aborted!" );
}
// evaluate the return value:
if ( theCancelMethodReplyJobImpl.isReturnValue() ) {
if ( theCancelMethodReplyJobImpl.getReturnValue() instanceof Boolean ) {
final boolean returnValue = ( (Boolean) theCancelMethodReplyJobImpl.getReturnValue() ).booleanValue();
if ( returnValue ) {
theProxyControl.dispose();
}
return returnValue;
}
}
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descripter." );
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.size();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void destroy() {
if ( !isDestroyed() ) {
super.destroy();
closeQuietly();
_instanceHandler.clear();
_instanceHandler = null;
}
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* A reply from a {@link RemoteServer} is pushed to the {@link RemoteClient}
* using this method.
*
* @param aMethodReply An object of type BlueprintMethodReply from which the
* result of a request can be retrieved.
*/
private void pushMethodReply( Reply aMethodReply ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
final ProxyControl theProxyControl = _instanceHandler.getProxyControl( aMethodReply.getInstanceId() );
if ( theProxyControl == null ) {
throw new IllegalArgumentException( "Unable to retrieve the the proxy control assosiated the instance ID <" + aMethodReply.getInstanceId() + "> of the given method reply." );
}
theProxyControl.pushMethodReply( aMethodReply );
}
// /////////////////////////////////////////////////////////////////////////
// DEBUG:
// /////////////////////////////////////////////////////////////////////////
/**
* For testing purposes, any job can be manually pushed into the job process
* to the receiver.
*
* @param aJob The job to be pushed to the receiver.
*
* @throws IOException Thrown in case the operation failed due to an I/O
* malfunction such as the input- or output-connection (of an input-
* and output-connection pair) is not available or being
* disconnected.
*/
protected void doSendJob( Message aJob ) throws IOException {
toReceiver( aJob );
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
protected void digest( Message aJob ) throws DigestException {
try {
if ( aJob == null ) {
return;
}
// -----------------------------------------------------------------
// CLOSE CONNECTION JOB:
// -----------------------------------------------------------------
if ( aJob instanceof CloseConnectionMessage ) {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Received a close connection job to <" + getClass().getName() + "> and closing connection." );
}
close( (CloseConnectionMessage) aJob );
}
// -----------------------------------------------------------------
// CANCEL METHOD REPLY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof CancelMethodReplyMessage theReplyJob ) {
if ( theReplyJob.getInstanceId() == null ) {
return;
}
final String aInstanceId = theReplyJob.getInstanceId();
if ( !_instanceHandler.hasMethodReplyDescriptor( aInstanceId ) ) {
if ( isClosed() || !isOpened() ) {
return;
}
else {
LOGGER.log( Level.WARNING, "Unknown cancel method reply instance ID <" + aInstanceId + "> for job of type <" + theReplyJob.getClass().getName() + ">, expected a known instance ID." );
// throw new UnknownInstanceIdRuntimeException( "Unknown instance ID <" + aInstanceId + ">, expected a known instance ID." );
}
}
final Object tmpReply = _instanceHandler.getMethodReplyDescriptor( aInstanceId );
if ( !( tmpReply instanceof CancelMethodReplyMessage ) ) {
throw new InvalidMethodReplyRuntimeException( "Expected a \"" + CancelMethodReplyMessage.class.getName() + "\"." );
}
final CancelMethodReplyMessage waitingReplyRemotorJob = (CancelMethodReplyMessage) tmpReply;
waitingReplyRemotorJob.setReply( theReplyJob );
synchronized ( waitingReplyRemotorJob ) {
waitingReplyRemotorJob.notifyAll();
}
}
// -----------------------------------------------------------------
// METHOD REPLY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof MethodReplyMessage ) {
pushMethodReply( (Reply) aJob );
}
// -----------------------------------------------------------------
// PULISH SUBJECT JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof PublishSubjectMessage ) {
try {
final boolean returnValue = publishClassDescriptor( (ClassDescriptor) aJob );
final PublishSubjectReplyMessage thePublishSubjectReplyJob = new PublishSubjectReplyMessage();
thePublishSubjectReplyJob.setInstanceId( aJob.getInstanceId() );
thePublishSubjectReplyJob.setException( null );
thePublishSubjectReplyJob.setReturnValue( returnValue );
thePublishSubjectReplyJob.setHasReply( true );
toReceiver( thePublishSubjectReplyJob );
}
catch ( VetoException aException ) {
final PublishSubjectReplyMessage theMethodReplyJob = new PublishSubjectReplyMessage();
theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
theMethodReplyJob.setException( aException );
theMethodReplyJob.setReturnValue( null );
theMethodReplyJob.setHasReply( false );
toReceiver( theMethodReplyJob );
}
}
// -----------------------------------------------------------------
// SIGN-OFF SUBJECT JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof SignOffSubjectMessage ) {
final InstanceId theInstanceDescriptor = aJob;
final boolean isSignOff = signoffInstanceDescriptor( theInstanceDescriptor );
final PublishSubjectReplyMessage theMethodReplyJob = new PublishSubjectReplyMessage();
theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
theMethodReplyJob.setReturnValue( isSignOff );
theMethodReplyJob.setException( null );
theMethodReplyJob.setHasReply( true );
toReceiver( theMethodReplyJob );
}
}
catch ( IOException aException ) {
closeOnException();
throw new DigestException( "Digesting the job caused an exception to be thrown.", aException );
}
}
/**
* {@inheritDoc}
*/
@Override
protected synchronized void close( CloseConnectionMessage aJob ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "CLOSE called on <" + getClass().getName() + "> with job <" + aJob + ">; connection status is " + getConnectionStatus() + "." );
}
if ( !isClosed() ) {
signOffAllProxies();
_instanceHandler.lock();
final RetryTimeout theRetryTimeout = new RetryTimeout( IoTimeout.NORM.getTimeMillis(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getValue() );
while ( ( isBusy() ) && theRetryTimeout.hasNextRetry() ) {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while being BUSY for <" + SleepLoopTime.NORM.getTimeMillis() + "> ms." );
}
theRetryTimeout.nextRetry();
}
super.close( aJob );
if ( isBusy() ) {
LOGGER.log( Level.WARNING, "Still being BUSY even after reaching the timeout of <" + IoTimeout.NORM.getTimeMillis() + "> ms, closing connection nonetheless." );
}
try {
super.close();
}
catch ( IOException e ) {
LOGGER.log( Level.WARNING, "Unable to close malfunctioning connection.", e );
}
onClosed();
}
}
// /////////////////////////////////////////////////////////////////////////
// SIGNALS:
// /////////////////////////////////////////////////////////////////////////
/**
* Hook when a proxy is about to be published.
*
* @param aType The type to be stored by the property.
*
* @throws VetoException Thrown to signal that an operation is being vetoed
* by a third party observing the invocation of the given operation.
*/
protected void onPublishProxy( Class> aType ) throws VetoException {}
/**
* Hook when a proxy has been published.
*
* @param aProxy The proxy to being published.
*/
protected void onProxyPublished( Object aProxy ) {}
/**
* Hook when a proxy has been signed-off.
*
* @param aProxy The proxy which has been signed-off.
*/
protected void onProxySignedOff( Object aProxy ) {}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* A request from a proxy control only is pushed to the remotor using this
* method.
*
* @param aMethodRequestDescriptor An object of type BlueprintMethodRequest
* from which the request can be retrieved.
*
* @throws IOException the open exception
*/
void pushMethodRequest( MethodRequest aMethodRequestDescriptor ) throws IOException {
final MethodRequestMessage theMethodRequestJob = new MethodRequestMessage();
theMethodRequestJob.setMethodRequestDescriptor( aMethodRequestDescriptor );
toReceiver( theMethodRequestJob );
}
/**
* Only cleanup, no close!.
*/
private void clearOnException() {
ProxyDescriptor eProxyDescriptor;
ProxyControl eProxyControl;
synchronized ( _instanceHandler ) {
final Iterator e = _instanceHandler.proxyDescriptors();
while ( e.hasNext() ) {
eProxyDescriptor = e.next();
eProxyControl = _instanceHandler.getProxyControl( eProxyDescriptor.getInstanceId() );
if ( eProxyControl != null ) {
eProxyControl.dispose();
}
onProxySignedOff( eProxyDescriptor.getProxy() );
}
}
_instanceHandler.clear();
}
/**
* Only cleanup, no close!.
*/
private void signOffAllProxies() {
boolean hasException = false;
synchronized ( _instanceHandler ) {
ProxyControl eProxyControl;
final Iterator e = _instanceHandler.proxyControls();
while ( e.hasNext() ) {
eProxyControl = e.next();
try {
signOffProxy( eProxyControl.getProxy() );
}
catch ( IOException aException ) {
hasException = true;
}
}
if ( hasException == true ) {
clearOnException();
}
_instanceHandler.clear();
}
}
/**
* Closes the connection and disposes all proxy controls. This method is
* called whenever an exception indicated some not reparable damage on the
* connection so that the system is finished up and events are fired for all
* proxies being removed.
*/
private void closeOnException() {
clearOnException();
try {
super.close();
}
catch ( IOException e ) {
LOGGER.log( Level.WARNING, "Unable to close malfunctioning connection.", e );
}
onClosed();
}
/**
* This method is used to publish a subject which resides in a
* {@link RemoteServer}.
*
* @param aClassDescriptor The {@link ClassDescriptor} describing the
* subject.
*
* @return True in case the according proxy has been published successfully.
*
* @throws VetoException the veto exception
*/
private boolean publishClassDescriptor( ClassDescriptor aClassDescriptor ) throws VetoException {
assert ( aClassDescriptor != null );
assert ( aClassDescriptor.getInstanceId() != null );
if ( _instanceHandler.hasProxyControl( aClassDescriptor.getInstanceId() ) ) {
throw new DuplicateInstanceIdRuntimeException( "The instance ID <" + aClassDescriptor.getInstanceId() + "> of the given class descriptor is already in use." );
}
final String aInstanceId = aClassDescriptor.getInstanceId();
onPublishProxy( aClassDescriptor.getType() );
final ProxyControl theProxyControl = new ProxyControl( aClassDescriptor );
final ProxyDescriptor proxyDescriptor = new ProxyDescriptor( aClassDescriptor, theProxyControl.getProxy() );
_instanceHandler.addProxyControl( theProxyControl, aInstanceId );
_instanceHandler.addProxyDescriptor( proxyDescriptor, aInstanceId );
onProxyPublished( proxyDescriptor.getProxy() );
return true;
}
/**
* Signs off an instance previously published using the
* publishClassDescriptor() method. This method is to be used by services
* only - the toolkit uses the corresponding signoffInstanceDescriptor().
*
* @param aInstanceDescriptor An object of type GenericInstanceDescriptor
* containing the information needed to sign-off an instance.
*
* @return True is returned if the instance could be signed-off
*/
private boolean signoffInstanceDescriptor( InstanceId aInstanceDescriptor ) {
if ( aInstanceDescriptor == null ) {
throw new NullPointerException( "Expected an object of type instead of a null value in argument ." );
}
if ( aInstanceDescriptor.getInstanceId() == null ) {
throw new NullPointerException( "Expected an object of type instead of a null value when retrieving the instance ID of argument ." );
}
final String aInstanceId = aInstanceDescriptor.getInstanceId();
if ( _instanceHandler.hasSignedOffInstanceId( aInstanceId ) ) {
return true;
}
if ( !_instanceHandler.hasProxyControl( aInstanceId ) ) {
throw new UnknownInstanceIdRuntimeException( "Expected a known instance ID in argument ." );
}
final ProxyDescriptor theProxyDescriptor = _instanceHandler.getProxyDescriptor( aInstanceId );
final ProxyControl theProxyControl = _instanceHandler.getProxyControl( aInstanceId );
onProxySignedOff( theProxyDescriptor.getProxy() );
theProxyControl.lock();
waitForActiveSessions( theProxyControl );
_instanceHandler.removeProxyDescriptor( aInstanceId );
theProxyControl.dispose();
return true;
}
/**
* Wait for active sessions.
*
* @param aProxyControl the proxy control
*/
private void waitForActiveSessions( ProxyControl aProxyControl ) {
final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_ACTIVE_SESSIONS_TIMEOUT_IN_MS, WAIT_FOR_ACTIVE_SESSIONS_LOOPS );
while ( ( aProxyControl.isBusy() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while proxy \"" + aProxyControl.getProxy() + "\" (<" + aProxyControl.getClass() + ">) having ACTIVE SESSIONS for <" + WAIT_FOR_REPLY_LOOPS + "> ms." );
}
theRetryTimeout.nextRetry( aProxyControl );
}
}
/**
* Retrieves the {@link ProxyDescriptor} describing a proxy.
*
* @param aProxy The proxy for which to get the descriptor.
*
* @return The according descriptor or null if none such descriptor was
* found.
*/
private ProxyDescriptor getProxyDescriptor( Object aProxy ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
ProxyDescriptor eProxyDescriptor;
synchronized ( _instanceHandler ) {
final Iterator e = _instanceHandler.proxyDescriptors();
while ( e.hasNext() ) {
eProxyDescriptor = e.next();
if ( eProxyDescriptor.getProxy() == aProxy ) {
return eProxyDescriptor;
}
}
}
return null;
}
// /////////////////////////////////////////////////////////////////////////
// PROXY CONTROL:
// /////////////////////////////////////////////////////////////////////////
/**
* The {@link ProxyControl} is used to manage a proxy being provided from a
* subject be the {@link RemoteServer}. This is an inner interface as it is
* just required by one implementation internally; in comparison other
* interface's implementations are required by at least two different
* implementations (e.g. client and server).
*/
private class ProxyControl implements InstanceId, InvocationHandler, Lockable, Disposable, ProxyAccessor, BusyAccessor {
private static final boolean IS_THROW_UNKNOWN_INSTANCE_ID_EXCETIONS_ENABLED = false;
private final ClassDescriptor _instanceDescriptor;
private int _hasActiveSessionsCount = 0;
private boolean _isDisposeable = false;
private boolean _isLocked = false;
private boolean _isDisposed = false;
private final Object _proxy;
private final Generator _sessionIdGenerator = new UniqueIdGenerator( SESSION_ID_LENGTH );
private final Map _sessionIds2MethodReply = new HashMap<>();
/**
* Creates a new ProxyControl object.
*
* @param aInstanceDescriptor Description is currently not available!
*/
ProxyControl( ClassDescriptor aInstanceDescriptor ) {
// |--> put the GenericProxy interface into the proxy's definition:
final int length = aInstanceDescriptor.getType().getInterfaces().length;
final Class>[] interfaceArray = aInstanceDescriptor.getType().getInterfaces();
final Class>[] proxyInterfaceArray = new Class[length + 1];
for ( int i = 0; i < length; i++ ) {
proxyInterfaceArray[i] = interfaceArray[i];
}
proxyInterfaceArray[length] = Disposable.class;
if ( Disposable.class.isAssignableFrom( aInstanceDescriptor.getType() ) ) {
_isDisposeable = true;
}
// <--|
_proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), proxyInterfaceArray, this );
_instanceDescriptor = aInstanceDescriptor;
}
/**
* {@inheritDoc}
*/
@Override
public void dispose() {
synchronized ( getProxy() ) {
_isDisposed = true;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object obj ) {
return super.equals( obj );
}
/**
* Returns the class descriptor which has been used to create the Proxy
* object.
*
* @return An object of type BlueprintClassDescriptor which has been
* used to generate the Proxy object.
*/
public ClassDescriptor getInstanceDescriptor() {
return _instanceDescriptor;
}
/**
* {@inheritDoc}
*/
@Override
public String getInstanceId() {
return _instanceDescriptor.getInstanceId();
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public P getProxy() {
return (P) _proxy;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isBusy() {
return ( _hasActiveSessionsCount > 0 );
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return super.hashCode();
}
/**
* Invoke.
*
* @param proxy the proxy
* @param method the method
* @param arguments the arguments
*
* @return the object
*
* @throws Throwable the throwable
*/
@Override
public Object invoke( Object proxy, Method method, Object[] arguments ) throws Throwable {
_hasActiveSessionsCount++;
if ( ( "equals".equals( method.getName() ) ) && ( arguments != null ) && ( arguments.length == 1 ) ) {
_hasActiveSessionsCount--;
return getProxy() == arguments[0];
}
// |--> test the GenericProxy interface method:
if ( ( "isProxyDisposed".equals( method.getName() ) ) && ( arguments == null ) ) {
_hasActiveSessionsCount--;
// return new Boolean(isProxyDisposed());
return isUnusable();
}
// |--> test the GenericDisposeablePublic interface method:
if ( ( "isDisposed".equals( method.getName() ) ) && ( arguments == null ) ) {
if ( _isDisposeable ) {
if ( isUnusable() ) {
_hasActiveSessionsCount--;
return true;
}
}
else {
_hasActiveSessionsCount--;
return isUnusable();
}
}
// <--|
if ( ( "hashCode".equals( method.getName() ) ) && ( arguments == null ) ) {
_hasActiveSessionsCount--;
return super.hashCode();
}
// if ((method.getName().equals("equals")) && (arguments != null)
// && (arguments.length == 1))
// return new Boolean(equals(arguments[0]));
if ( isUnusable() ) {
// |--> !!!
// if ((method.getName().equals("equals")) && (arguments !=
// null) && (arguments.length == 1)) {
// _hasActiveSessionsCount --;
// return new Boolean(super.equals(arguments[0])); / ?
// return new Boolean(getProxy() == arguments[0]); / !
// }
// <--| !!!
// |--> !!!
if ( ( "toString".equals( method.getName() ) ) && ( arguments == null ) ) {
_hasActiveSessionsCount--;
return super.toString();
}
// <--| !!!
// |--> !!!
if ( ( "isDisposed".equals( method.getName() ) ) && ( arguments == null ) ) {
_hasActiveSessionsCount--;
return true;
}
// <--| !!!
throw new ProxyDisposedRuntimeException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is disposed!" );
}
String theSessionId = null;
synchronized ( _sessionIdGenerator ) {
if ( _sessionIdGenerator.hasNext() ) {
theSessionId = _sessionIdGenerator.next();
}
else {
_hasActiveSessionsCount--;
throw new IllegalStateException( "The ID generator is unable to create more unique IDs" );
}
}
if ( theSessionId == null ) {
_hasActiveSessionsCount--;
throw new IllegalStateException( "The ID generator is unable to create more unique IDs" );
}
Reply methodReply = null;
synchronized ( _sessionIds2MethodReply ) {
if ( _sessionIds2MethodReply.containsKey( theSessionId ) ) {
_hasActiveSessionsCount--;
throw new DuplicateSessionIdRuntimeException( "The session id generator seems to generatoe duplicate id objects. Sorry - aborting operation!" );
}
final String instanceId = getInstanceId();
methodReply = new ReplyDescriptor( instanceId, theSessionId );
_sessionIds2MethodReply.put( theSessionId, methodReply );
}
final MethodRequest methodRequest = new MethodRequestDescriptor( method, arguments, getInstanceId(), theSessionId );
pushMethodRequest( methodRequest );
// -----------------------------------------------------------------
// Pushed request - waiting for reply:
// -----------------------------------------------------------------
long timeout = WAIT_FOR_REPLY_TIMEOUT;
while ( !methodReply.hasReply() && ( timeout >= 0 ) && !isUnusable() && isOpened() ) {
synchronized ( methodReply ) {
try {
if ( !methodReply.hasReply() && ( timeout >= 0 ) && ( !isUnusable() ) ) {
methodReply.wait( WAIT_FOR_REPLY_LOOPS );
}
}
catch ( InterruptedException ie ) {}
}
timeout -= WAIT_FOR_REPLY_LOOPS;
}
_sessionIds2MethodReply.remove( theSessionId );
_hasActiveSessionsCount--;
if ( !methodReply.hasReply() ) {
if ( isUnusable() ) {
throw new ProxyDisposedRuntimeException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is disposed!" );
}
else if ( timeout < 0 ) {
throw new TimeoutIOException( WAIT_FOR_REPLY_TIMEOUT, "While processing the request a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot! Propably lost the connection (you propably should close the connection). Sorry - request aborted!" );
}
else {
throw new IOException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is did not recieve the expected remote reply - unkown aCause!" );
}
}
// -----------------------------------------------------------------
if ( methodReply.isException() ) {
throw methodReply.getException();
}
else {
if ( ( "equals".equals( method.getName() ) ) && ( arguments != null ) && ( arguments.length == 1 ) && ( methodReply.getReturnValue() instanceof Boolean ) ) {
final boolean returnValue = ( (Boolean) methodReply.getReturnValue() ).booleanValue();
return super.equals( arguments[0] ) | returnValue;
}
return methodReply.getReturnValue();
}
}
/**
* Used to push a reply to a request.
*
* @param aMethodReply An object of type BlueprintMethodReply used to
* encapsulate a reply from a method call.
*/
public void pushMethodReply( Reply aMethodReply ) {
if ( aMethodReply == null ) {
return;
}
if ( !getInstanceId().equals( aMethodReply.getInstanceId() ) ) {
if ( !isUnusable() ) {
throw new UnknownInstanceIdRuntimeException( "The instance id of the argument ( object) is not the same as the expected instance id !!! Sorry - aborting operation!" );
}
else {
return;
}
}
if ( !_sessionIds2MethodReply.containsKey( aMethodReply.getSessionId() ) ) {
if ( !isUnusable() ) {
if ( IS_THROW_UNKNOWN_INSTANCE_ID_EXCETIONS_ENABLED ) {
throw new UnknownInstanceIdRuntimeException( "The session id of the argument ( object) is not the same as the expected session id !!! Sorry - aborting operation!" );
}
else {
return;
}
}
else {
return;
}
}
final Reply waitingMethodReply = _sessionIds2MethodReply.remove( aMethodReply.getSessionId() );
if ( waitingMethodReply == null ) {
if ( !isUnusable() ) {
throw new IllegalStateException( "No prepared method reply object found." );
}
else {
return;
}
}
waitingMethodReply.setReply( aMethodReply );
synchronized ( waitingMethodReply ) {
waitingMethodReply.notifyAll();
}
}
/**
* Lock.
*/
@Override
public void lock() {
synchronized ( getProxy() ) {
_isLocked = true;
}
}
/**
* Unlock.
*/
@Override
public void unlock() {
synchronized ( getProxy() ) {
_isLocked = false;
}
}
/**
* Checks if is locked.
*
* @return true, if is locked
*/
@Override
public boolean isLocked() {
return _isLocked;
}
/**
* {@inheritDoc}
*/
public boolean isUnusable() {
return ( ( _isDisposed ) || ( _isLocked ) || ( RemoteClient.this.isClosed() ) || ( RemoteClient.this.isEmpty() ) );
}
}
// /////////////////////////////////////////////////////////////////////////
// INSTANCE HANDLER:
// /////////////////////////////////////////////////////////////////////////
/**
* Helper class for providing synchronized access to vital data structures.
*/
private class InstanceHandler implements Lockable, BusyAccessor {
// /////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////
private final HashMap _instanceIdsToProxyControl = new HashMap<>();
private final HashMap _instanceIdsToProxyDescriptor = new HashMap<>();
private final HashMap _instanceIdsToMethodReplyDescriptor = new HashMap<>();
private final Set _signedOffInstanceIds = Collections.newSetFromMap( new WeakHashMap<>() );
// /////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////
/**
* Adds a {@link ProxyControl} to this instance associated to the given
* instance ID.
*
* @param aProxyControl A {@link ProxyControl} to be added.
* @param aInstanceId The instance ID to which the {@link ProxyControl}
* is to be associated.
*
* @return True if the operation has been performed successfully.
*/
boolean addProxyControl( ProxyControl aProxyControl, String aInstanceId ) {
synchronized ( this ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( hasProxyControl( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a proxy control; an unused instance ID must be provided." );
}
}
else if ( hasProxyControl( aInstanceId ) ) {
return false;
}
_instanceIdsToProxyControl.put( aInstanceId, aProxyControl );
}
return true;
}
/**
* Adds a {@link ProxyDescriptor} to this instance associated to the
* given instance ID.
*
* @param aPreoxyDescriptor A {@link ProxyDescriptor} to be added.
* @param aInstanceId The instance ID to which the
* {@link ProxyDescriptor} is to be associated.
*
* @return True if the operation has been performed successfully.
*/
boolean addProxyDescriptor( ProxyDescriptor aPreoxyDescriptor, String aInstanceId ) {
synchronized ( this ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( hasProxyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a proxy descriptor; an unused instance ID must be provided." );
}
}
else if ( hasProxyDescriptor( aInstanceId ) ) {
return false;
}
_instanceIdsToProxyDescriptor.put( aInstanceId, aPreoxyDescriptor );
}
return true;
}
/**
* Adds a reply descriptor to this instance associated to the given
* instance ID.
*
* @param aMethodReplyDescriptor An object of type
* BlueprintMethodReplyDescriptor to be added.
* @param aInstanceId The aInstanceId to which the reply descriptor is
* to be associated.
*
* @return True if the operation has been performed successfully.
*/
boolean addReplyDescriptor( Reply aMethodReplyDescriptor, String aInstanceId ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( hasMethodReplyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a method reply descriptor; an unused instance ID must be provided." );
}
}
else if ( hasMethodReplyDescriptor( aInstanceId ) ) {
return false;
}
_instanceIdsToMethodReplyDescriptor.put( aInstanceId, aMethodReplyDescriptor );
return true;
}
/**
* Clears the {@link InstanceHandler} instances.
*/
synchronized void clear() {
_instanceIdsToProxyControl.clear();
_instanceIdsToProxyDescriptor.clear();
_instanceIdsToMethodReplyDescriptor.clear();
_signedOffInstanceIds.clear();
}
/**
* Returns the proxy control associated to the given instance ID.
*
* @param aInstanceId The instance ID which's proxy control is to be
* returned.
*
* @return An object of type BlueprintMethodProxyControl which is
* associated to the given instance ID
*/
ProxyControl getProxyControl( String aInstanceId ) {
synchronized ( this ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasProxyControl( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." );
}
}
else if ( !hasProxyControl( aInstanceId ) ) {
return null;
}
}
return _instanceIdsToProxyControl.get( aInstanceId );
}
/**
* Returns the proxy descriptor associated to the given instance ID.
*
* @param aInstanceId The instance ID which's proxy descriptor is to be
* returned.
*
* @return An object of type BlueprintMethodProxyDescriptor which is
* associated to the given instance ID
*/
ProxyDescriptor getProxyDescriptor( String aInstanceId ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasProxyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy descriptor; a valid instance ID must be provided." );
}
}
else if ( !hasProxyDescriptor( aInstanceId ) ) {
return null;
}
return _instanceIdsToProxyDescriptor.get( aInstanceId );
}
/**
* Returns the reply descriptor associated to the given instance ID.
*
* @param aInstanceId The instance ID which's reply descriptor is to be
* returned.
*
* @return An object of type BlueprintMethodReplyDescriptor which is
* associated to the given instance ID
*/
Reply getMethodReplyDescriptor( String aInstanceId ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasMethodReplyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any method reply descriptor; a valid instance ID must be provided." );
}
}
else if ( !hasMethodReplyDescriptor( aInstanceId ) ) {
return null;
}
return _instanceIdsToMethodReplyDescriptor.get( aInstanceId );
}
/**
* Tests if the given instance ID is in the list of the waiting proxy
* controls.
*
* @param aInstanceId The instance ID for which the existence of a proxy
* control is to be tested.
*
* @return True if there is a proxy control associated to the instance
* id.
*/
boolean hasProxyControl( String aInstanceId ) {
return _instanceIdsToProxyControl.containsKey( aInstanceId );
}
/**
* Tests if the given instance ID is in the list of the waiting proxy
* descriptors.
*
* @param aInstanceId The instance ID for which the existence of a proxy
* descriptor is to be tested.
*
* @return True if there is a proxy descriptor associated to the
* instance ID
*/
boolean hasProxyDescriptor( String aInstanceId ) {
return _instanceIdsToProxyDescriptor.containsKey( aInstanceId );
}
/**
* Tests if the given instance id is in the list of the waiting reply
* descriptors.
*
* @param aInstanceId The instance ID for which the existence of a reply
* descriptor is to be tested.
*
* @return True if there is a reply descriptor associated to the
* instance ID
*/
boolean hasMethodReplyDescriptor( String aInstanceId ) {
return _instanceIdsToMethodReplyDescriptor.containsKey( aInstanceId );
}
/**
* Tests if the provided instance ID has already been used and is not in
* use any more.
*
* @param aInstanceId The ID which is to be tested.
*
* @return Description is currently not available!
*/
boolean hasSignedOffInstanceId( String aInstanceId ) {
return ( _signedOffInstanceIds.contains( aInstanceId ) );
}
/**
* Returns an {@link Iterator} containing the currently managed
* {@link ProxyControl} instances.
*
* @return An {@link Iterator} containing the currently managed
* {@link ProxyControl} instances.
*/
synchronized Iterator proxyControls() {
return new ArrayList( _instanceIdsToProxyControl.values() ).iterator();
}
/**
* Returns an {@link Iterator} containing the currently managed
* {@link ProxyDescriptor} instances.
*
* @return An {@link Iterator} containing the currently managed
* {@link ProxyDescriptor} instances.
*/
Iterator proxyDescriptors() {
return _instanceIdsToProxyDescriptor.values().iterator();
}
/**
* Removes the proxy descriptor from this instance which has been
* associated to the given instance ID.
*
* @param aInstanceId The aInstanceId which's proxy descriptor is to be
* removed.
*
* @return The proxy descriptor which has been removed.
*/
ProxyDescriptor removeProxyDescriptor( String aInstanceId ) {
synchronized ( this ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasProxyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proy descriptor; a valid instance ID must be provided." );
}
if ( !hasProxyControl( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." );
}
if ( hasSignedOffInstanceId( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." );
}
}
else {
if ( !hasProxyDescriptor( aInstanceId ) ) {
return null;
}
if ( !hasProxyControl( aInstanceId ) ) {
return null;
}
if ( hasSignedOffInstanceId( aInstanceId ) ) {
return null;
}
}
addSignedOffInstanceId( aInstanceId );
removeProxyControl( aInstanceId );
return _instanceIdsToProxyDescriptor.remove( aInstanceId );
}
}
/**
* Removes the reply descriptor from this instance which has been
* associated to the given instance ID.
*
* @param aInstanceId The aInstanceId which's reply descriptor is to be
* removed.
*
* @return The reply descriptor which has been removed.
*/
Reply removeMethodReplyDescriptor( String aInstanceId ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasMethodReplyDescriptor( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any method reply descriptor; a valid instance ID must be provided." );
}
}
else if ( !hasMethodReplyDescriptor( aInstanceId ) ) {
return null;
}
return _instanceIdsToMethodReplyDescriptor.remove( aInstanceId );
}
/**
* Returns the number if {@link ProxyDescriptor} instances being
* currently managed.
*
* @return The number if {@link ProxyDescriptor} instances being
* currently managed.
*/
int size() {
return _instanceIdsToProxyDescriptor.size();
}
/**
* Adds an instance ID to the list of signed-off instance IDs.
*
* @param aInstanceId The instance ID to be added.
*
* @return True if added, false if already added before.
*/
private boolean addSignedOffInstanceId( String aInstanceId ) {
synchronized ( this ) {
return ( _signedOffInstanceIds.add( aInstanceId ) );
}
}
/**
* Removes the {@link ProxyControl} identified by the given instance ID.
*
* @param aInstanceId The instance ID which's {@link ProxyControl} is to
* be removed.
*
* @return The {@link ProxyControl} which has been removed.
*/
private ProxyControl removeProxyControl( String aInstanceId ) {
synchronized ( this ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !hasProxyControl( aInstanceId ) ) {
throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." );
}
}
else if ( !hasProxyControl( aInstanceId ) ) {
return null;
}
return _instanceIdsToProxyControl.remove( aInstanceId );
}
}
/**
* Determines whether the currently managed number of
* {@link ProxyDescriptor} instances is zero.
*
* @return True in case the currently managed number of
* {@link ProxyDescriptor} instances is zero.
*/
boolean isEmpty() {
return _instanceIdsToProxyDescriptor.isEmpty();
}
/**
* Checks if is busy.
*
* @return true, if is busy
*/
@Override
public synchronized boolean isBusy() {
if ( !_instanceIdsToMethodReplyDescriptor.isEmpty() ) {
return false;
}
final Iterator e = proxyControls();
ProxyControl eProxyControl;
while ( e.hasNext() ) {
eProxyControl = e.next();
if ( eProxyControl.isBusy() ) {
return true;
}
}
return false;
}
/**
* Lock.
*/
@Override
public synchronized void lock() {
final Iterator e = proxyControls();
while ( e.hasNext() ) {
e.next().lock();
}
}
/**
* Unlock.
*/
@Override
public synchronized void unlock() {
final Iterator e = proxyControls();
while ( e.hasNext() ) {
e.next().unlock();
}
}
/**
* Checks if is locked.
*
* @return true, if is locked
*/
@Override
public synchronized boolean isLocked() {
final Iterator e = proxyControls();
while ( e.hasNext() ) {
if ( !e.next().isLocked() ) {
return false;
}
}
return true;
}
}
}