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

org.refcodes.remoting.impls.AbstractRemote Maven / Gradle / Ivy

// /////////////////////////////////////////////////////////////////////////////
// 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.impls;

import java.io.Serializable;
import java.util.concurrent.ExecutorService;

import org.refcodes.collection.Properties;
import org.refcodes.component.CloseException;
import org.refcodes.component.ConnectionStatus;
import org.refcodes.component.Destroyable;
import org.refcodes.component.DigestException;
import org.refcodes.component.Digester;
import org.refcodes.component.OpenException;
import org.refcodes.component.Resetable;
import org.refcodes.component.ext.observer.OpenEvent;
import org.refcodes.component.impls.AbstractConnectableAutomaton;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryCounter;
import org.refcodes.controlflow.impls.RetryCounterImpl;
import org.refcodes.data.IoRetryCount;
import org.refcodes.data.LatencySleepTime;
import org.refcodes.data.LoopExtensionTime;
import org.refcodes.exception.ExceptionUtility;
import org.refcodes.exception.HiddenException;
import org.refcodes.factory.RecyclableTypeFactory;
import org.refcodes.factory.impls.AbstractRecyclableTypeFactory;
import org.refcodes.io.DatagramTransceiver;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.impls.RuntimeLoggerFactorySingleton;
import org.refcodes.mixin.Disposable.Disposedable;
import org.refcodes.remoting.CloseConnectionMessage;
import org.refcodes.remoting.Message;
import org.refcodes.remoting.Remote;
import org.refcodes.remoting.RemoteClient;
import org.refcodes.remoting.RemoteServer;
import org.refcodes.remoting.SignOffProxyMessage;

/**
 * Base class for {@link RemoteClient} and {@link RemoteServer} implementations
 * implementing various factories useful creating and reusing descriptor
 * instances. The factories provided are of type {@link RecyclableTypeFactory}.
 */
abstract class AbstractRemote extends AbstractConnectableAutomaton implements Remote {

	/**
	 * Static instance ID in case we have a job regarding the
	 * {@link RemoteServer} or the {@link RemoteClient} as a whole. Its length
	 * must be != {@link #INSTANCE_ID_LENGTH}.
	 */
	static final String STATIC_INSTANCE_ID = "static_instance";

	/**
	 * Static session ID in case we have a job regarding the
	 * {@link RemoteServer} or the {@link RemoteClient} as a whole. Its length
	 * must be != {@link #SESSION_ID_LENGTH}.
	 */
	static final String STATIC_SESSION_ID = "static_session";

	/**
	 * The length of an instance id. Each published object is assigned an
	 * instance id). A method request / reply is assigned to an instance id
	 * additional to it's session id in order to identify the object / proxy
	 * belonging to tjhe session.
	 */
	static final int INSTANCE_ID_LENGTH = 16;

	/**
	 * The length of a session id (for method requests / replies). Each method
	 * request is assigned a unique session id, which will be used in method
	 * replies.
	 */
	static final int SESSION_ID_LENGTH = 32;

	/**
	 * Any request made waits this amount of time (in milliseconds) for a reply
	 * - if no reply reaches the requester within this time a
	 * ConnectionUnpredictableException is thrown.
	 */
	static final long WAIT_FOR_REPLY_TIMEOUT = 30000;

	/**
	 * When signing off an object / proxy, the proxy is given this amount of
	 * time (in milliseconds) to close it's active session before it is disposed
	 * and removed. Any remaining active sessions (method calls) will be replied
	 * with a ConnectionRuntimeException if the timeout was shorter then the
	 * time needed for the active sessions to finish.
	 */

	static final long WAIT_FOR_ACTIVE_SESSIONS_TIMEOUT_IN_MS = (WAIT_FOR_REPLY_TIMEOUT * 2 / 3);

	/**
	 * The amount of time (in milliseconds) to wait and poll for active
	 * sessions.
	 */
	static final int WAIT_FOR_ACTIVE_SESSIONS_LOOPS = 250;

	/**
	 * The amount of time (in milliseconds) to wait and poll for replies.
	 * Normally while waiting all waiting threads are woken up when a reply for
	 * this threads is received.
	 */
	static final int WAIT_FOR_REPLY_LOOPS = 250;

	/**
	 * Set to true in case the (not really time consuming) consistency checks
	 * are to be performed.
	 */
	static final boolean PERFORM_CONSISTENCY_CHECKS = false;

	/**
	 * Set to true to pool objects which are ought to be created in vast numbers
	 * in order to reduce object creation and to lessen memory consumption.
	 */
	static final boolean ENABLE_OBJECT_POOLING = true;

	/**
	 * For debugging purposes; additional log output to follow the trace of a
	 * job through the {@link RemoteServer} and the {@link RemoteClient}.
	 */
	static final boolean ENABLE_EXTENDED_DEBUG_LOGGING = true;

	private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();

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

	DatagramTransceiver _transceiver;
	JobReceiverDaemon _jobReceiverDaemon = null;
	private JobDigesterDaemonFactoryImpl _jobDigesterDaemonFactory = new JobDigesterDaemonFactoryImpl();
	private ExecutorService _executorService;
	private boolean _isDestroyed = false;

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

	/**
	 * Constructs a {@link AbstractRemote} provided with the given
	 * {@link InterProcessDigester} and configured with an
	 * {@link ExecutorService} something like
	 * {@link ControlFlowUtility#createDaemonExecutorService()}.
	 * 
	 * @param aTransceiver The {@link DatagramTransceiver} to be used for
	 *        establishing a connection with the according counterpart.
	 */
	public AbstractRemote() {
		this( null );
	}

	/**
	 * Constructs a {@link AbstractRemote} configured with the given
	 * {@link ExecutorService} required for thread generation in an JEE
	 * environment.
	 * 
	 * @param aTransceiver The {@link DatagramTransceiver} to be used for
	 *        establishing a connection with the according counterpart.
	 * 
	 * @param aExecutorService The {@link ExecutorService} to be used, when null
	 *        then an {@link ExecutorService} something like
	 *        {@link ControlFlowUtility#createDaemonExecutorService()} is then
	 *        retrieved.
	 */
	public AbstractRemote( ExecutorService aExecutorService ) {
		if ( aExecutorService == null ) {
			_executorService = ControlFlowUtility.createDaemonExecutorService();
		}
		else {
			_executorService = ControlFlowUtility.toManagedExecutorService( aExecutorService );
		}
	}

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

	@Override
	public boolean isOpenable( DatagramTransceiver aConnection ) {
		return super.isOpenable() && aConnection.isOpened();
	}

	@Override
	public synchronized void open( DatagramTransceiver aTransceiver ) throws OpenException {
		_transceiver = aTransceiver;
		ControlFlowUtility.throwIllegalStateException( _isDestroyed || isOpened() );
		ConnectionStatus theConnectionStatus = _transceiver.getConnectionStatus();
		if ( theConnectionStatus != ConnectionStatus.OPENED ) { throw new OpenException( "Unable to open the remote of type <" + getClass().getName() + "> as the underlying transcivier of type <" + _transceiver.getClass().getName() + "> is in status " + theConnectionStatus + " (not open; please open it beofrehand)." ); }
		_jobReceiverDaemon = new JobReceiverDaemon();
		LOGGER.debug( "Starting job receiver daemon <" + _jobReceiverDaemon.getClass().getName() + ">." );
		_executorService.execute( _jobReceiverDaemon );
		setConnectionStatus( ConnectionStatus.OPENED );
	}

	@Override
	public synchronized void close() throws CloseException {
		if ( isClosed() ) return;
		ControlFlowUtility.throwIllegalStateException( _isDestroyed );
		if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
			LOGGER.debug( "CLOSE called on <" + getClass().getName() + ">." );
		}
		super.close();
		// if (_jobReceiverDaemon != null ) {
		_jobReceiverDaemon.dispose();
		_jobReceiverDaemon = null;
		// }
		if ( _transceiver.isOpened() ) {
			_transceiver.close();
		}

	}

	@Override
	public void destroy() {
		if ( !_isDestroyed ) {
			try {
				close();
			}
			catch ( CloseException e ) {
				LOGGER.warn( "Destroying failed as of: " + ExceptionUtility.toMessage( e ), e );
			}
			finally {
				_executorService.shutdownNow();
				try {
					_transceiver.close();
				}
				catch ( CloseException e ) {
					LOGGER.warn( "Destroying failed as of: " + ExceptionUtility.toMessage( e ), e );
				}
				finally {
					_isDestroyed = true;
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// ADAPTER:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * The {@link #digest(Message)} method is invoked in order to trigger
	 * processing of the provided {@link Message}, e.g. start execution
	 * depending on the {@link Message} instance being provided from the
	 * outside. A {@link RemoteClient} digests such a {@link Message} different
	 * than a {@link RemoteServer}.
	 * 
	 * This method is inspired by the {@link Digester#digest(Object)} method.
	 * 
	 * @param aJob The {@link Message} to be digested.
	 * 
	 * @throws DigestException Thrown in case digesting (processing) a job by a
	 *         {@link Digester#digest(Object)} caused problems; the cause is
	 *         usually wrapped by this {@link Exception}.
	 * 
	 * @see Digester
	 */
	abstract protected void digest( Message aJob ) throws DigestException;

	/**
	 * Forwards an {@link Message} from this sender to another receiver. A
	 * sub-class invokes this method when it sends an {@link Message} to the
	 * receiver counterpart. The communication path is to be implemented by the
	 * sub-class.
	 * 
	 * @param aJob the {@link Message} to be forwarded to this receiver from the
	 *        sender counterpart.
	 * 
	 * @throws OpenException Thrown in case opening or accessing an open line
	 *         (connection, junction, link) caused problems.
	 */
	protected void toReceiver( Message aJob ) throws OpenException {
		try {
			_transceiver.writeDatagram( aJob );
		}
		catch ( OpenException aException ) {
			if ( !(aJob instanceof CloseConnectionMessage) ) { throw aException; }
		}
		// @formatter:off
		/*
			if ( isOpened() && _transceiver.isOpened() ) {
				_transceiver.writeDatagram( aJob );
			}
			else {
				LOGGER.warn( "Ignoring remote job <" + aJob.getClass().getName() + "> as the connection is not open; connection status is " + getConnectionStatus() + " (transciver connection status is " + _transceiver.getConnectionStatus() + ")." );
			}
			
		*/
		// @formatter:on
	}

	/**
	 * Provides an {@link Message} from another sender to this receiver. A
	 * sub-class invokes this method when it receives an {@link Message} from a
	 * sender counterpart. The communication path is to be implemented by the
	 * sub-class.
	 * 
	 * @param aJob the {@link Message} to be forwarded to this receiver from the
	 *        sender counterpart.
	 */
	protected void fromSender( Message aJob ) {
		ControlFlowUtility.throwIllegalStateException( _isDestroyed );
		if ( isOpened() ) {
			if ( aJob instanceof CloseConnectionMessage ) {
				if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
					LOGGER.debug( "Received a close connection job to <" + getClass().getName() + ">; closing connection." );
				}
				synchronized ( this ) {
					if ( isOpened() ) {
						close( (CloseConnectionMessage) aJob );
					}
				}
			}
			else {
				JobDigesterDaemonImpl theJobDigesterDaemon = _jobDigesterDaemonFactory.toInstance();
				theJobDigesterDaemon.setInterProcessJob( aJob );
				_executorService.execute( theJobDigesterDaemon );
			}
		}
		else {
			// -----------------------------------------------------------------
			// Ignore close connection jobs as them may be received after the
			// connection has logically been closed though still an underlying
			// or encapsulating system my still invoke this method.
			// -----------------------------------------------------------------
			if ( !(aJob instanceof CloseConnectionMessage) ) {
				throw new IllegalStateException( "Received a job <" + aJob + "> though this remote \"" + getClass().getName() + "\" is not in opened status; connection status is " + getConnectionStatus() );
			}
			else {
				LOGGER.info( "Ignoring job of type \"" + aJob.getClass().getName() + "\" being received whilst connection has been closed." );
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Hook method for retrieving the {@link ExecutorService}.
	 * 
	 * @return The {@link ExecutorService} as used by this implementation.
	 */
	protected ExecutorService getExecutorService() {
		return _executorService;
	}

	/**
	 * Closes the {@link Remote} upon a received {@link CloseConnectionMessage}.
	 * Some client ID might be considered in order to determine whether to close
	 * all or just one of many connections.
	 * 
	 * This default implementation calls {@link #close()} (setting the
	 * {@link ConnectionStatus} to be {@link ConnectionStatus#CLOSED}); advanced
	 * implementations just closing dedicated connections may keep the
	 * {@link Remote} itself open (and skip calling {@link #close()} and close
	 * only a dedicated connection identified by the meta data of the
	 * {@link CloseConnectionMessage}.
	 * 
	 * @param aJob The {@link CloseConnectionMessage} being received for closing
	 *        the connection.
	 */
	protected synchronized void close( CloseConnectionMessage aJob ) {
		// Close via business-logic, complete remote is to be shut down:
		if ( aJob == null ) {
			try {
				toReceiver( new CloseConnectionMessageImpl() );
			}
			catch ( OpenException aMessage ) {
				LOGGER.warn( "Sending a close connection job to the communication couinterpart caused an exception.", aMessage );
			}
		}
	}

	/**
	 * Hook to support the {@link Destroyable} interface, determines the
	 * destroyed status property; set to true when {@link #destroy()} is called.
	 * 
	 * @return The destroyed status property.
	 */
	protected boolean isDestroyed() {
		return _isDestroyed;
	}

	// /////////////////////////////////////////////////////////////////////////
	// SIGNALS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Hook when the connection has been opened. In case an observable
	 * {@link DatagramTransceiver} is being supported by a sub-class, then the
	 * {@link DatagramTransceiver}'s {@link OpenEvent} can be used to trigger
	 * this method.
	 */
	protected void onOpened() {}

	/**
	 * Hook when the connection has been closed.
	 */
	protected void onClosed() {}

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

	// /////////////////////////////////////////////////////////////////////////
	// JOB RECEIVER DAEMON:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * A daemon process used to read datagrams from the
	 * {@link DatagramTransceiver}.
	 */
	protected class JobReceiverDaemon implements Runnable, Disposedable {

		private boolean _isDisposed = false;

		// /////////////////////////////////////////////////////////////////////
		// DAEMON:
		// /////////////////////////////////////////////////////////////////////

		@Override
		public void run() {
			try {
				Message eJob;
				while ( !_isDisposed && isOpened() ) {
					if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
						LOGGER.debug( "Waiting for Datagram ..." );
					}
					eJob = (Message) _transceiver.readDatagram();
					if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
						LOGGER.debug( "Read datagram <" + eJob + ">." );
					}
					if ( AbstractRemote.this.isOpened() ) {
						fromSender( eJob );
					}
					else {
						LOGGER.info( "Ignoring job of type \"" + eJob.getClass().getName() + "\" being received whilst connection has been closed." );
					}
				}
			}
			catch ( OpenException | InterruptedException aException ) {
				boolean isTransceiverClosed = _transceiver.isClosed();
				synchronized ( AbstractRemote.this ) {
					if ( AbstractRemote.this.isOpened() ) {
						try {
							close();
						}
						catch ( CloseException e ) {
							LOGGER.warn( "Unable to close the malfunctioning connection: " + ExceptionUtility.toMessage( e ), e );
						}
					}
				}
				if ( !isTransceiverClosed ) {
					LOGGER.warn( "Caught an exception in daemon while reading jobs from the transceiver (receiver); the connection status is " + getConnectionStatus() + " (will get closed if not already); the transceiver's connection status is " + getConnectionStatus() + ": " + ExceptionUtility.toMessage( aException ), aException );
					throw new HiddenException( aException );
				}
			}
			finally {
				// @formatter:off
				// LOGGER.debug( "Terminating job receiver daemon <" + JobReceiverDaemon.this.getClass().getName() + ">." );
				// @formatter:on
			}
		}

		/**
		 * Waits for the {@link DatagramTransceiver} to open up; in case it is
		 * open, then true is returned; in case it does not open within a given
		 * period of time then false is returned.
		 * 
		 * @return True in case the {@link DatagramTransceiver} is (finally)
		 *         open.
		 */
		private boolean isOpened() {
			if ( !AbstractRemote.this.isOpened() ) {
				return false;
			}
			else if ( _transceiver.isOpened() && AbstractRemote.this.isOpened() ) {
				return true;
			}
			else if ( AbstractRemote.this.isOpened() && !_transceiver.isOpened() ) {
				RetryCounter theRetryCounter = new RetryCounterImpl( IoRetryCount.NORM.getNumber(), LatencySleepTime.NORM.getMilliseconds(), LoopExtensionTime.NORM.getMilliseconds() );
				while ( AbstractRemote.this.isOpened() && theRetryCounter.hasNextRetry() && !_transceiver.isOpened() ) {
					// @formatter:off
					if ( ENABLE_EXTENDED_DEBUG_LOGGING ) LOGGER.debug( "Wait loop <" + theRetryCounter.getRetryCount() + "> while waiting for OPEN for <" + WAIT_FOR_REPLY_LOOPS + "> ms; connection status is " + getConnectionStatus() + " (transceiver connection status is " + _transceiver.getConnectionStatus() + ").");
					// @formatter:on
					theRetryCounter.nextRetry();
				}
			}
			if ( _transceiver.isOpened() && AbstractRemote.this.isOpened() ) { return true; }
			return false;
		}

		@Override
		public void dispose() {
			_isDisposed = true;
		}

		@Override
		public boolean isDisposed() {
			return _isDisposed;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// JOB DIGESTTER DAEMON:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * A daemon to process a received {@link Message}.
	 */
	protected class JobDigesterDaemonImpl implements Runnable, Resetable {

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

		private JobDigesterDaemonFactoryImpl _daemonFactory;
		private Message _job = null;

		// /////////////////////////////////////////////////////////////////////
		// CONSTRRUCTORS:
		// /////////////////////////////////////////////////////////////////////

		public JobDigesterDaemonImpl( JobDigesterDaemonFactoryImpl aDaemonFactory ) {
			_daemonFactory = aDaemonFactory;
		}

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

		@Override
		public void reset() {
			setInterProcessJob( null );
		}

		@Override
		public void run() {
			try {
				digest( _job );
			}
			catch ( DigestException aException ) {
				if ( !(_job instanceof SignOffProxyMessage)  || isOpened() )
				LOGGER.warn( "Unable to digest the job \"" + _job.getClass().getName() + "\" as of a digest exception: \"" + ExceptionUtility.toMessage( aException ) + "\"", aException );
			}
			finally {
				_daemonFactory.recycleInstance( this );
			}
		}

		/**
		 * Sets the {@link Message} attribute.
		 * 
		 * @param aJob The job to be processed.
		 */
		public void setInterProcessJob( Message aJob ) {
			_job = aJob;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// JOB DIGESTER DAEMON FACTORY:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * A {@link RecyclableTypeFactory} creating {@link JobDigesterDaemonImpl}
	 * instances.
	 */
	protected class JobDigesterDaemonFactoryImpl extends AbstractRecyclableTypeFactory {

		@Override
		protected JobDigesterDaemonImpl newInstance() {
			return new JobDigesterDaemonImpl( this );
		}

		@Override
		protected JobDigesterDaemonImpl newInstance( Properties aProperties ) {
			return new JobDigesterDaemonImpl( this );
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// CANCEL METHOD REPLY JOB FACTORY:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Implementation of the {@link RecyclableTypeFactory} creating instances of
	 * the according type.
	 */
	protected static class CancelMethodReplyJobFactoryImpl extends AbstractRecyclableTypeFactory {

		@Override
		protected CancelMethodReplyMessageImpl newInstance() {
			return new CancelMethodReplyMessageImpl();
		}

		@Override
		protected CancelMethodReplyMessageImpl newInstance( Properties aProperties ) {
			return new CancelMethodReplyMessageImpl();
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// PUBLISH SUBJECT REPLY JOB FACTORY:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Implementation of the {@link RecyclableTypeFactory} creating instances of
	 * the according type.
	 */
	protected static class PublishSubjectReplyJobFactoryImpl extends AbstractRecyclableTypeFactory {

		@Override
		protected PublishSubjectReplyMessageImpl newInstance() {
			return new PublishSubjectReplyMessageImpl();
		}

		@Override
		protected PublishSubjectReplyMessageImpl newInstance( Properties aProperties ) {
			return new PublishSubjectReplyMessageImpl();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy