All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.remoting.RemoteClientImpl Maven / Gradle / Ivy

Go to download

Artifact with proxy functionality for plain remote procedure calls (RPC), a POJO alternative to RMI.

There is a newer version: 3.3.9
Show newest version
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// 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/LICENSE-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.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 org.refcodes.component.CloseException;
import org.refcodes.component.DigestException;
import org.refcodes.component.OpenException;
import org.refcodes.component.OpenTimeoutException;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryTimeout;
import org.refcodes.controlflow.RetryTimeoutImpl;
import org.refcodes.data.IoTimeout;
import org.refcodes.data.LoopSleepTime;
import org.refcodes.data.RetryLoopCount;
import org.refcodes.exception.VetoException;
import org.refcodes.generator.Generator;
import org.refcodes.generator.UniqueIdGeneratorImpl;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.RuntimeLoggerFactorySingleton;
import org.refcodes.mixin.BusyAccessor;
import org.refcodes.mixin.DisposedAccessor;
import org.refcodes.mixin.Lockable;

/**
 * Abstract implementation of the {@link RemoteClient}'s base functionality.
 */
public class RemoteClientImpl extends AbstractRemote implements RemoteClient {

	private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private InstanceHandler _instanceHandler = new InstanceHandler();

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Instantiates a new remote client impl.
	 */
	public RemoteClientImpl() {
		super();
	}

	/**
	 * Instantiates a new remote client impl.
	 *
	 * @param aExecutorService the executor service
	 */
	public RemoteClientImpl( ExecutorService aExecutorService ) {
		super( aExecutorService );
	}

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clear() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		signOffAllProxies();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void close() {
		if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
			LOGGER.info( "CLOSE called on <" + getClass().getName() + ">." );
		}
		close( null );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isBusy() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		return _instanceHandler.isBusy();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasProxy( Object proxy ) {
		return getProxyDescriptor( proxy ) != null;
	}

	/**
	 * Gets the proxy.
	 *
	 * @param  the generic type
	 * @param aType the type
	 * @return the proxy
	 * @throws AmbiguousProxyException the ambiguous proxy exception
	 * @throws NoSuchProxyException the no such proxy exception
	 */
	@SuppressWarnings("unchecked")
	@Override
	public  T getProxy( Class aType ) throws AmbiguousProxyException, NoSuchProxyException {
		Iterator e = proxies();
		Object eProxy, 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;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasProxy( Class aType ) {
		Iterator e = proxies();
		Object eProxy;
		while ( e.hasNext() ) {
			eProxy = e.next();
			if ( aType.isAssignableFrom( eProxy.getClass() ) ) {
				return true;
			}

		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator proxies() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
		ArrayList theProxyList = new ArrayList( _instanceHandler.size() );
		synchronized ( _instanceHandler ) {
			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();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean signOffProxy( Object aProxy ) throws OpenException {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		if ( aProxy == null ) {
			return false;
		}
		ProxyDescriptor theProxyDescriptor = getProxyDescriptor( aProxy );
		if ( theProxyDescriptor == null ) {
			return false;
		}
		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 !!!" );
			}
		}
		SignOffProxyMessageImpl theSignOffProxyJob = new SignOffProxyMessageImpl();
		theSignOffProxyJob.setInstanceId( theProxyDescriptor.getInstanceId() );
		CancelMethodReplyMessageImpl theCancelMethodReplyJobImpl = new CancelMethodReplyMessageImpl();
		theCancelMethodReplyJobImpl.setInstanceId( theInstanceId );
		theCancelMethodReplyJobImpl.setHasReply( false );
		_instanceHandler.addReplyDescriptor( theCancelMethodReplyJobImpl, theInstanceId );

		try {
			toReceiver( theSignOffProxyJob );
		}
		catch ( OpenException aException ) {
			theProxyControl.dispose();
			theProxyControl.unlock();
			_instanceHandler.removeMethodReplyDescriptor( theInstanceId );

			if ( aException.getCause() instanceof IOException ) {
				closeOnException();
			}
			throw aException;
		}

		RetryTimeout theRetryTimeout = new RetryTimeoutImpl( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
		while ( !theCancelMethodReplyJobImpl.hasReply() && theRetryTimeout.hasNextRetry() && isOpened() && !theProxyControl.isDisposed() ) {
			// @formatter:off
			if ( ENABLE_EXTENDED_DEBUG_LOGGING ) 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 OpenTimeoutException( 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 ) {
				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();
			close();
			_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.
	 */
	void pushMethodReply( Reply aMethodReply ) {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		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 OpenException 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 OpenException {
		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 ( ENABLE_EXTENDED_DEBUG_LOGGING ) LOGGER.info( "Received a close connection job to <" + getClass().getName() + ">; closing connection." );
				close( (CloseConnectionMessage) aJob );
			}
			// -----------------------------------------------------------------
			// CANCEL METHOD REPLY JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof CancelMethodReplyMessage ) {
				CancelMethodReplyMessage replyRemotorJob = (CancelMethodReplyMessage) aJob;
				if ( replyRemotorJob.getInstanceId() == null ) {
					return;
				}
				String aInstanceId = replyRemotorJob.getInstanceId();
				if ( !_instanceHandler.hasMethodReplyDescriptor( aInstanceId ) ) {
					throw new UnknownInstanceIdRuntimeException( "Unkwnown instance ID <" + aInstanceId + ">, expected a known instance ID." );
				}
				Object tmpReply = _instanceHandler.getMethodReplyDescriptor( aInstanceId );
				if ( !(tmpReply instanceof CancelMethodReplyMessage) ) {
					throw new InvalidMethodReplyRuntimeException( "Excpected a \"" + CancelMethodReplyMessage.class.getName() + "\"." );
				}
				CancelMethodReplyMessage waitingReplyRemotorJob = (CancelMethodReplyMessage) tmpReply;
				waitingReplyRemotorJob.setReply( replyRemotorJob );
				synchronized ( waitingReplyRemotorJob ) {
					waitingReplyRemotorJob.notifyAll();
				}
			}
			// -----------------------------------------------------------------
			// METHOD REPLY JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof MethodReplyMessage ) {
				pushMethodReply( (Reply) aJob );
			}
			// -----------------------------------------------------------------
			// PULISH SUBJECT JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof PublishSubjectMessage ) {
				try {
					boolean returnValue = publishClassDescriptor( (InstanceDescriptor) aJob );
					PublishSubjectReplyMessageImpl thePublishSubjectReplyJob = new PublishSubjectReplyMessageImpl();
					thePublishSubjectReplyJob.setInstanceId( aJob.getInstanceId() );
					thePublishSubjectReplyJob.setException( null );
					thePublishSubjectReplyJob.setReturnValue( returnValue );
					thePublishSubjectReplyJob.setHasReply( true );
					toReceiver( thePublishSubjectReplyJob );
				}
				catch ( VetoException aException ) {
					PublishSubjectReplyMessageImpl theMethodReplyJob = new PublishSubjectReplyMessageImpl();
					theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
					theMethodReplyJob.setException( aException );
					theMethodReplyJob.setReturnValue( null );
					theMethodReplyJob.setHasReply( false );
					toReceiver( theMethodReplyJob );
				}
			}
			// -----------------------------------------------------------------
			// SIGN-OFF SUBJECT JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof SignOffSubjectMessage ) {
				InstanceId theInstanceDescriptor = aJob;
				boolean isSignOff = signoffInstanceDescriptor( theInstanceDescriptor );
				PublishSubjectReplyMessageImpl theMethodReplyJob = new PublishSubjectReplyMessageImpl();
				theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
				theMethodReplyJob.setReturnValue( isSignOff );
				theMethodReplyJob.setException( null );
				theMethodReplyJob.setHasReply( true );
				toReceiver( theMethodReplyJob );
			}
		}
		catch ( OpenException aException ) {
			closeOnException();
			throw new DigestException( "Digesting the job caued a cause exception to be thrown.", aException );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected synchronized void close( CloseConnectionMessage aJob ) {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
			LOGGER.info( "CLOSE called on <" + getClass().getName() + "> with job <" + aJob + ">; connection status is " + getConnectionStatus() + "." );
		}
		if ( !isClosed() ) {
			signOffAllProxies();
			_instanceHandler.lock();
			RetryTimeout theRetryTimeout = new RetryTimeoutImpl( IoTimeout.NORM.getMilliseconds(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getNumber() );
			while ( (isBusy()) && theRetryTimeout.hasNextRetry() ) {
				if ( ENABLE_EXTENDED_DEBUG_LOGGING ) LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while being BUSY for <" + LoopSleepTime.NORM.getMilliseconds() + "> ms." );
				theRetryTimeout.nextRetry();
			}
			super.close( aJob );
			if ( isBusy() ) {
				LOGGER.warn( "Still being BUSY even after reaching the timeout of <" + IoTimeout.NORM.getMilliseconds() + "> ms, closing connection nonetheless." );
			}
			try {
				super.close();
			}
			catch ( CloseException e ) {
				LOGGER.warn( "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 OpenException the open exception
	 */
	void pushMethodRequest( MethodRequest aMethodRequestDescriptor ) throws OpenException {
		MethodRequestMessageImpl theMethodRequestJob = new MethodRequestMessageImpl();
		theMethodRequestJob.setMethodRequestDescriptor( aMethodRequestDescriptor );
		toReceiver( theMethodRequestJob );
	}

	/**
	 * Only cleanup, no close!.
	 */
	private void clearOnException() {
		ProxyDescriptor eProxyDescriptor;
		ProxyControl eProxyControl;
		synchronized ( _instanceHandler ) {
			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;
			Iterator e = _instanceHandler.proxyControls();
			while ( e.hasNext() ) {
				eProxyControl = e.next();
				try {
					signOffProxy( eProxyControl.getProxy() );
				}
				catch ( OpenException 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 ( CloseException e ) {
			LOGGER.warn( "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 InstanceDescriptor} describing the
	 *        subject.
	 * @return True in case the according proxy has been published successfully.
	 * @throws VetoException the veto exception
	 */
	private boolean publishClassDescriptor( InstanceDescriptor 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." );
		}
		String aInstanceId = aClassDescriptor.getInstanceId();

		onPublishProxy( aClassDescriptor.getType() );

		ProxyControl theProxyControl = new ProxyControlImpl( aClassDescriptor );
		ProxyDescriptor proxyDescriptor = new ProxyDescriptorImpl( 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 ." );
		}
		String aInstanceId = aInstanceDescriptor.getInstanceId();
		if ( _instanceHandler.hasSignedOffInstanceId( aInstanceId ) ) {
			return true;
		}
		if ( !_instanceHandler.hasProxyControl( aInstanceId ) ) {
			throw new UnknownInstanceIdRuntimeException( "Expected a known instance ID in argument ." );
		}
		ProxyDescriptor theProxyDescriptor = _instanceHandler.getProxyDescriptor( aInstanceId );
		ProxyControlImpl theProxyControl = (ProxyControlImpl) _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 ) {
		RetryTimeout theRetryTimeout = new RetryTimeoutImpl( WAIT_FOR_ACTIVE_SESSIONS_TIMEOUT_IN_MS, WAIT_FOR_ACTIVE_SESSIONS_LOOPS );
		while ( (aProxyControl.isBusy()) && theRetryTimeout.hasNextRetry() && isOpened() ) {
			if ( ENABLE_EXTENDED_DEBUG_LOGGING ) 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 ) {
			Iterator e = _instanceHandler.proxyDescriptors();
			while ( e.hasNext() ) {
				eProxyDescriptor = e.next();
				if ( eProxyDescriptor.getProxy() == aProxy ) return eProxyDescriptor;
			}
		}
		return null;
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// PROXY CONTROL:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Implementation of the {@link ProxyControl} interface providing
	 * functionality to manage a proxy being published from a subject by a
	 * {@link RemoteServer}.
	 */
	private class ProxyControlImpl implements ProxyControl {

		private final static boolean IS_THROW_UNKNOWN_INSTANCE_ID_EXCETIONS_ENABLED = false;
		private InstanceDescriptor _classDescriptor;
		private int _hasActiveSessionsCount = 0;
		private boolean _isDisposeable = false;
		private boolean _isLocked = false;
		private boolean _isProxyDisposed = false;
		private Object _proxy;
		private Generator _sessionIdGenerator = new UniqueIdGeneratorImpl( SESSION_ID_LENGTH );
		private Map _sessionIds2MethodReply = new HashMap();

		/**
		 * Creates a new ProxyControl object.
		 *
		 * @param aClassDescriptor Description is currently not available!
		 */
		ProxyControlImpl( InstanceDescriptor aClassDescriptor ) {
			// |--> put the GenericProxy interface into the proxy's definition:
			int length = aClassDescriptor.getType().getInterfaces().length;
			Class[] interfaceArray = aClassDescriptor.getType().getInterfaces();
			Class[] proxyInterfaceArray = new Class[length + 2];
			for ( int i = 0; i < length; i++ )
				proxyInterfaceArray[i] = interfaceArray[i];
			proxyInterfaceArray[length] = DisposedAccessor.class;
			proxyInterfaceArray[length + 1] = Disposedable.class;
			if ( Disposedable.class.isAssignableFrom( aClassDescriptor.getType() ) ) _isDisposeable = true;
			// <--|
			_proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), proxyInterfaceArray, this );
			_classDescriptor = aClassDescriptor;
		}

		/**
		 * Dispose.
		 */
		@Override
		public void dispose() {
			synchronized ( getProxy() ) {
				_isProxyDisposed = true;
			}
		}

		/**
		 * Equals.
		 *
		 * @param obj the obj
		 * @return true, if successful
		 */
		@Override
		public boolean equals( Object obj ) {
			return super.equals( obj );
		}

		/**
		 * Gets the class descriptor.
		 *
		 * @return the class descriptor
		 */
		@Override
		public InstanceDescriptor getClassDescriptor() {
			return _classDescriptor;
		}

		/**
		 * Gets the instance id.
		 *
		 * @return the instance id
		 */
		@Override
		public String getInstanceId() {
			return _classDescriptor.getInstanceId();
		}

		/**
		 * Gets the proxy.
		 *
		 * @param 

the generic type * @return the proxy */ @SuppressWarnings("unchecked") @Override public

P getProxy() { return (P) _proxy; } /** * Checks if is busy. * * @return true, if is busy */ @Override public boolean isBusy() { return (_hasActiveSessionsCount > 0); } /** * Hash code. * * @return the int */ @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 ( (method.getName().equals( "equals" )) && (arguments != null) && (arguments.length == 1) ) { _hasActiveSessionsCount--; return getProxy() == arguments[0]; } // |--> test the GenericProxy interface method: if ( (method.getName().equals( "isProxyDisposed" )) && (arguments == null) ) { _hasActiveSessionsCount--; // return new Boolean(isProxyDisposed()); return isProxyUnusable(); } // |--> test the GenericDisposeablePublic interface method: if ( (method.getName().equals( "isDisposed" )) && (arguments == null) ) { if ( _isDisposeable ) { if ( isProxyUnusable() ) { _hasActiveSessionsCount--; return true; } } else { _hasActiveSessionsCount--; return isProxyUnusable(); } } // <--| if ( (method.getName().equals( "hashCode" )) && (arguments == null) ) { _hasActiveSessionsCount--; return super.hashCode(); } // if ((method.getName().equals("equals")) && (arguments != null) // && (arguments.length == 1)) // return new Boolean(equals(arguments[0])); if ( isProxyUnusable() ) { // |--> !!! // 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 ( (method.getName().equals( "toString" )) && (arguments == null) ) { _hasActiveSessionsCount--; return super.toString(); } // <--| !!! // |--> !!! if ( (method.getName().equals( "isDisposed" )) && (arguments == null) ) { _hasActiveSessionsCount--; return true; } // <--| !!! throw new ProxyDisposedRuntimeException( "The proxy object <" + getClassDescriptor().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!" ); } String instanceId = getInstanceId(); methodReply = new ReplyDescriptorImpl( instanceId, theSessionId ); _sessionIds2MethodReply.put( theSessionId, methodReply ); } MethodRequest methodRequest = new MethodRequestDescriptorImpl( method, arguments, getInstanceId(), theSessionId ); pushMethodRequest( methodRequest ); // ----------------------------------------------------------------- // Pushed request - waiting for reply: // ----------------------------------------------------------------- long timeout = WAIT_FOR_REPLY_TIMEOUT; while ( !methodReply.hasReply() && (timeout >= 0) && !isProxyUnusable() && isOpened() ) { synchronized ( methodReply ) { try { if ( !methodReply.hasReply() && (timeout >= 0) && (!isProxyUnusable()) ) { methodReply.wait( WAIT_FOR_REPLY_LOOPS ); } } catch ( InterruptedException ie ) {} } timeout -= WAIT_FOR_REPLY_LOOPS; } _sessionIds2MethodReply.remove( theSessionId ); _hasActiveSessionsCount--; if ( !methodReply.hasReply() ) { if ( isProxyUnusable() ) { throw new ProxyDisposedRuntimeException( "The proxy object <" + getClassDescriptor().getType().getName() + "> is disposed!" ); } else if ( timeout < 0 ) { throw new OpenTimeoutException( 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 OpenException( "The proxy object <" + getClassDescriptor().getType().getName() + "> is did not recieve the expected remote reply - unkown cause!" ); } } // ----------------------------------------------------------------- if ( methodReply.isException() ) throw methodReply.getException(); else { if ( (method.getName().equals( "equals" )) && (arguments != null) && (arguments.length == 1) && (methodReply.getReturnValue() instanceof Boolean) ) { boolean returnValue = ((Boolean) methodReply.getReturnValue()).booleanValue(); return super.equals( arguments[0] ) | returnValue; } return methodReply.getReturnValue(); } } /** * Checks if is disposed. * * @return true, if is disposed */ @Override public boolean isDisposed() { return (isProxyUnusable()); } /** * Push method reply. * * @param aMethodReply the method reply */ @Override public void pushMethodReply( Reply aMethodReply ) { if ( aMethodReply == null ) { return; } if ( !getInstanceId().equals( aMethodReply.getInstanceId() ) ) { if ( !isProxyUnusable() ) { 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 ( !isProxyUnusable() ) { 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; } } Reply waitingMethodReply = _sessionIds2MethodReply.remove( aMethodReply.getSessionId() ); if ( waitingMethodReply == null ) { if ( !isProxyUnusable() ) { 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; } /** * Checks if is proxy unusable. * * @return Description is currently not available! */ private boolean isProxyUnusable() { return ((_isProxyDisposed) || (_isLocked) || (RemoteClientImpl.this.isClosed()) || (RemoteClientImpl.this.isEmpty())); } } // ///////////////////////////////////////////////////////////////////////// // INSTANCE HANDLER: // ///////////////////////////////////////////////////////////////////////// /** * Helper class for providing synchronized access to vital data structures. */ private class InstanceHandler implements Lockable, BusyAccessor { // ///////////////////////////////////////////////////////////////////// // VARIABLES: // ///////////////////////////////////////////////////////////////////// private HashMap _instanceIdsToProxyControl = new HashMap(); private HashMap _instanceIdsToProxyDescriptor = new HashMap(); private HashMap _instanceIdsToMethodReplyDescriptor = new HashMap(); private 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 synchronized public boolean isBusy() { if ( !_instanceIdsToMethodReplyDescriptor.isEmpty() ) { return false; } Iterator e = proxyControls(); ProxyControl eProxyControl; while ( e.hasNext() ) { eProxyControl = e.next(); if ( eProxyControl.isBusy() ) return true; } return false; } /** * Lock. */ @Override public synchronized void lock() { Iterator e = proxyControls(); while ( e.hasNext() ) { e.next().lock(); } } /** * Unlock. */ @Override public synchronized void unlock() { Iterator e = proxyControls(); while ( e.hasNext() ) { e.next().unlock(); } } /** * Checks if is locked. * * @return true, if is locked */ @Override public synchronized boolean isLocked() { Iterator e = proxyControls(); while ( e.hasNext() ) { if ( !e.next().isLocked() ) { return false; } } return true; } } }