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.impls.RemoteServerImpl 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 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.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import org.refcodes.collection.Properties;
import org.refcodes.component.CloseException;
import org.refcodes.component.DigestException;
import org.refcodes.component.OpenException;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryTimeout;
import org.refcodes.controlflow.impls.RetryTimeoutImpl;
import org.refcodes.data.IoTimeout;
import org.refcodes.data.LoopSleepTime;
import org.refcodes.data.RetryLoopCount;
import org.refcodes.exception.ExceptionAccessor;
import org.refcodes.exception.ExceptionUtility;
import org.refcodes.exception.HiddenException;
import org.refcodes.exception.VetoException;
import org.refcodes.factory.RecyclableTypeFactory;
import org.refcodes.factory.impls.AbstractRecyclableTypeFactory;
import org.refcodes.generator.Generator;
import org.refcodes.generator.impls.UniqueIdGeneratorImpl;
import org.refcodes.io.SerializeUtility;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.impls.RuntimeLoggerFactorySingleton;
import org.refcodes.mixin.BusyAccessor;
import org.refcodes.remoting.CloseConnectionMessage;
import org.refcodes.remoting.DuplicateInstanceIdRuntimeException;
import org.refcodes.remoting.InstanceDescriptor;
import org.refcodes.remoting.InstanceId;
import org.refcodes.remoting.InvalidMethodReplyRuntimeException;
import org.refcodes.remoting.InvalidMethodRequestRuntimeException;
import org.refcodes.remoting.Message;
import org.refcodes.remoting.MethodRequest;
import org.refcodes.remoting.MethodRequestMessage;
import org.refcodes.remoting.PublishSubjectReplyMessage;
import org.refcodes.remoting.RemoteClient;
import org.refcodes.remoting.RemoteServer;
import org.refcodes.remoting.Reply;
import org.refcodes.remoting.SignOffProxyMessage;
import org.refcodes.remoting.SubjectDescriptor;
import org.refcodes.remoting.SubjectInstance;
import org.refcodes.remoting.UnknownInstanceIdRuntimeException;
/**
* Abstract implementation of the {@link RemoteServer}'s base functionality.
*/
public class RemoteServerImpl extends AbstractRemote implements RemoteServer {
private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private InstanceHandler _instanceHandler = new InstanceHandler();
private CancelMethodReplyJobFactoryImpl _cancelMethodReplyJobFactory = new CancelMethodReplyJobFactoryImpl();
private MethodReplyJobFactoryImpl _methodReplyJobFactory = new MethodReplyJobFactoryImpl();
private SignOffJobFactoryImpl _signOffJobFactory = new SignOffJobFactoryImpl();
private PublishProxyJobFactoryImpl _publishProxyJobFactory = new PublishProxyJobFactoryImpl();
private PublishSubjectReplyJobFactoryImpl _publishSubjectReplyJobFactory = new PublishSubjectReplyJobFactoryImpl();
private Generator _instanceIdGenerator = new UniqueIdGeneratorImpl( INSTANCE_ID_LENGTH );
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
public RemoteServerImpl() {
super( null );
}
public RemoteServerImpl( ExecutorService aExecutorService ) {
super( aExecutorService );
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
@Override
public void clear() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
signOffAllSubjects();
}
@Override
public synchronized void close() {
if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
LOGGER.info( "CLOSE called on <" + getClass().getName() + ">." );
}
close( null );
}
/**
* {@inheritDoc}
*
* -------------------------------------------------------------------------
* TODO: Enhance the {@link #isBusy()} method, e.g. are there any threads
* active?
* -------------------------------------------------------------------------
*/
@Override
public boolean isBusy() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isBusy();
}
@Override
public boolean hasSubject( Object obj ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.hasSubject( obj );
}
@Override
public Iterator subjects() {
synchronized ( _instanceHandler ) {
List thjeList = new ArrayList( _instanceHandler.getSubjects() );
return thjeList.iterator();
}
}
@Override
public boolean isEmpty() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isEmpty();
}
@Override
public boolean publishSubject( Object aSubject ) throws OpenException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
synchronized ( _instanceHandler ) {
SubjectDescriptor eObjDescriptor;
Iterator e = _instanceHandler.subjectDescriptors();
while ( e.hasNext() ) {
eObjDescriptor = e.next();
if ( eObjDescriptor.getSubject() == aSubject ) return false;
}
if ( !_instanceHandler.hasSubject( aSubject ) ) {
String theInstanceId = null;
synchronized ( _instanceIdGenerator ) {
if ( _instanceIdGenerator.hasNext() ) theInstanceId = _instanceIdGenerator.next();
else {
throw new IllegalStateException( "The instance ID generator in use is unable to produce more instance IDs." );
}
}
if ( _instanceHandler.hasInstanceId( theInstanceId ) ) { throw new DuplicateInstanceIdRuntimeException( "The instance ID generator in use produces duplicate instance IDs." ); }
SubjectInstance theSubjectInstanceDescriptor = new SubjectInstanceDescriptorImpl( aSubject, theInstanceId );
InstanceDescriptor classDescriptor = new ClassDescriptorImpl( aSubject.getClass(), theInstanceId );
if ( _instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) { throw new DuplicateInstanceIdRuntimeException( "The instance ID <" + theInstanceId + "> is already in use by the internal instance handler." ); }
PublishSubjectMessageImpl thePublishSubjectJob = _publishProxyJobFactory.toInstance();
thePublishSubjectJob.setClassDescriptor( classDescriptor );
PublishSubjectReplyMessageImpl theMethodReplyRemotingJob = _publishSubjectReplyJobFactory.toInstance();
theMethodReplyRemotingJob.setInstanceId( theInstanceId );
theMethodReplyRemotingJob.setHasReply( false );
_instanceHandler.addReplyDescriptor( theMethodReplyRemotingJob, theInstanceId );
try {
toReceiver( thePublishSubjectJob );
}
catch ( OpenException aException ) {
LOGGER.warn( ExceptionUtility.toMessage( aException ), aException );
_instanceHandler.removeReplyDescriptor( theInstanceId );
if ( aException.getCause() instanceof IOException ) {
closeOnException();
}
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( theMethodReplyRemotingJob );
throw aException;
}
RetryTimeout theRetryTimeout = new RetryTimeoutImpl( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
while ( (!theMethodReplyRemotingJob.hasReply()) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( ENABLE_EXTENDED_DEBUG_LOGGING ) LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while waiting for method reply for <" + WAIT_FOR_REPLY_LOOPS + "> ms." );
theRetryTimeout.nextRetry( theMethodReplyRemotingJob );
}
_instanceHandler.removeReplyDescriptor( theInstanceId );
if ( !theMethodReplyRemotingJob.hasReply() ) {
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( theMethodReplyRemotingJob );
throw new IllegalStateException( "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)." );
}
if ( theMethodReplyRemotingJob.isException() ) {
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( theMethodReplyRemotingJob );
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descripter. Sorry - operation aborted!" );
}
if ( theMethodReplyRemotingJob.isReturnValue() ) {
if ( theMethodReplyRemotingJob.getReturnValue() instanceof Boolean ) {
boolean theReturnValue = ((Boolean) theMethodReplyRemotingJob.getReturnValue()).booleanValue();
if ( theReturnValue ) {
_instanceHandler.addSubjectDescriptor( theSubjectInstanceDescriptor, theInstanceId );
onSubjectPublished( theSubjectInstanceDescriptor.getSubject() );
}
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( theMethodReplyRemotingJob );
return theReturnValue;
}
}
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( theMethodReplyRemotingJob );
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descripter. Sorry - operation aborted!" );
}
return false;
}
}
@Override
public boolean signOffSubject( Object aSubject ) throws OpenException, VetoException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
String theSubjectId = toSubjectId( aSubject );
if ( theSubjectId == null ) { return false; }
return signoffInstanceDescriptor( new InstanceDescriptorImpl( theSubjectId ), -1 );
}
@Override
public boolean signOffSubject( Object aSubject, int aTimeoutInMs ) throws OpenException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
String theSubjectId = toSubjectId( aSubject );
if ( theSubjectId == null ) { return false; }
try {
return signoffInstanceDescriptor( new InstanceDescriptorImpl( theSubjectId ), aTimeoutInMs );
}
catch ( VetoException aException ) {
throw new HiddenException( aException );
}
}
@Override
public int size() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.size();
}
@Override
public synchronized void destroy() {
if ( !isDestroyed() ) {
super.destroy();
_instanceHandler.clear();
_instanceHandler = null;
_cancelMethodReplyJobFactory.clear();
_cancelMethodReplyJobFactory = null;
_methodReplyJobFactory.clear();
_methodReplyJobFactory = null;
_publishProxyJobFactory.clear();
_publishProxyJobFactory = null;
_publishSubjectReplyJobFactory.clear();
_publishSubjectReplyJobFactory = null;
_signOffJobFactory.clear();
_signOffJobFactory = null;
_instanceIdGenerator = null;
}
}
// /////////////////////////////////////////////////////////////////////////
// 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:
// /////////////////////////////////////////////////////////////////////////
@Override
public 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 );
}
// -----------------------------------------------------------------
// PUBLISH SUBJECT REPLY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof PublishSubjectReplyMessage ) {
PublishSubjectReplyMessage theReplyRemotingJob = (PublishSubjectReplyMessage) aJob;
if ( theReplyRemotingJob.getInstanceId() == null ) { return; }
String theInstanceId = theReplyRemotingJob.getInstanceId();
if ( !_instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) { throw new UnknownInstanceIdRuntimeException( "Expected an instance ID <" + theInstanceId + "> which was not found in order to reply to a request..." ); }
Object tmpReply = _instanceHandler.getMethodReplyDescriptor( theInstanceId );
if ( !(tmpReply instanceof PublishSubjectReplyMessage) ) { throw new InvalidMethodReplyRuntimeException( "Excpected a <" + PublishSubjectReplyMessage.class.toString() + "> to put the reply in." ); }
PublishSubjectReplyMessage thePublishSubjectReplyJob = (PublishSubjectReplyMessage) tmpReply;
thePublishSubjectReplyJob.setReply( theReplyRemotingJob );
synchronized ( thePublishSubjectReplyJob ) {
thePublishSubjectReplyJob.notifyAll();
}
}
// -----------------------------------------------------------------
// METHOD REQUEST JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof MethodRequestMessage ) {
MethodRequest theMethodRequestDescriptor = (MethodRequest) aJob;
Reply theMethodReplyDescriptor = pushMethodRequest( theMethodRequestDescriptor );
if ( theMethodReplyDescriptor == null ) { return; }
MethodReplyMessageImpl theMethodReplyJob = _methodReplyJobFactory.toInstance();
theMethodReplyJob.setMethodReplyDescriptor( theMethodReplyDescriptor );
toReceiver( theMethodReplyJob );
}
// -----------------------------------------------------------------
// SIGN-OFF PROXY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof SignOffProxyMessage ) {
InstanceId instanceDescriptor = aJob;
boolean theReturnValue = serviceSignoffInstanceDescriptor( instanceDescriptor );
CancelMethodReplyMessageImpl theCancelReplyRemotingJob = _cancelMethodReplyJobFactory.toInstance();
theCancelReplyRemotingJob.setInstanceId( instanceDescriptor.getInstanceId() );
theCancelReplyRemotingJob.setException( null );
theCancelReplyRemotingJob.setReturnValue( new Boolean( theReturnValue ) );
theCancelReplyRemotingJob.setHasReply( true );
try {
toReceiver( theCancelReplyRemotingJob );
}
catch ( OpenException aException ) { /* ignore */ }
}
}
catch ( OpenException aException ) {
throw new DigestException( aException );
}
}
/**
* Takes care of closing the {@link RemoteServer} depending on the
* {@link CloseConnectionMessage} received from the {@link RemoteClient}
* counterpart. In case a {@link RemoteServer} manages a one-to-many
* connection to different {@link RemoteClient} instances, then the
* {@link CloseConnectionMessage} provides meta data on which
* {@link RemoteClient} connection to close.
*
* By default just {@link #close()} is being invoked as if having a
* one-to-one connection.
*
* @param aJob The {@link CloseConnectionMessage} providing additional
* information on which {@link RemoteClient} connection to close.
*/
@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 + ">." );
}
if ( !isClosed() ) {
super.close( aJob );
RetryTimeout theRetryTimeout = new RetryTimeoutImpl( IoTimeout.NORM.getMilliseconds(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getNumber() );
while ( (isBusy()) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( ENABLE_EXTENDED_DEBUG_LOGGING ) {
LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while being BUSY for <" + LoopSleepTime.NORM.getMilliseconds() + "> ms." );
}
theRetryTimeout.nextRetry();
}
if ( isBusy() ) {
LOGGER.warn( "Still being BUSY even after reaching the timeout of <" + IoTimeout.NORM.getMilliseconds() + "> ms, closing connection nonetheless." );
}
signOffAllSubjects();
try {
super.close();
}
catch ( CloseException e ) {
LOGGER.warn( "Unable to close malfunctioning connection.", e );
}
onClosed();
}
}
// /////////////////////////////////////////////////////////////////////////
// SIGNALS:
// /////////////////////////////////////////////////////////////////////////
/**
* Hook when a subject has been published.
*
* @param aSubject The subject being published.
*/
protected void onSubjectPublished( Object aSubject ) {}
/**
* Hook when a subject has been signed-off.
*
* @param aSubject The subject which has been signed-off.
*/
protected void onSubjectSignedOff( Object aSubject ) {}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* Signs off the given subject identified by the provided
* {@link SubjectDescriptor}.
*
* @param aSubject The {@link SubjectDescriptor} describing the subject to
* be signed off.
*
* @return True in case there was such a subject which was being removed.
*
* @throws OpenException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
private boolean signOffSubject( SubjectDescriptor aSubject ) throws OpenException {
if ( isClosed() ) { return false; }
String eId = null;
Iterator e = _instanceHandler.instanceIds();
while ( e.hasNext() ) {
eId = e.next();
if ( _instanceHandler.getSubjectDescriptor( eId ) == aSubject ) break;
}
if ( eId == null ) { return false; }
boolean theResult;
try {
theResult = signoffInstanceDescriptor( new InstanceDescriptorImpl( eId ), 0 );
}
catch ( VetoException ve ) {
theResult = true;
}
return theResult;
}
/**
* Tries to determine the subject ID for the given subject.
*
* @param aSubject The subject for which to get the subject ID.
*
* @return The subject ID or null if there was none such subject found.
*/
private String toSubjectId( Object aSubject ) {
String eId = null;
boolean returnValue = false;
Iterator e = subjects();
while ( e.hasNext() ) {
Object eObj = e.next();
if ( eObj == aSubject ) returnValue = true;
}
if ( returnValue == false ) { return null; }
Iterator ee = subjects();
while ( ee.hasNext() ) {
Object eObj = ee.next();
if ( eObj instanceof SubjectInstanceDescriptorImpl ) {
if ( ((SubjectInstanceDescriptorImpl) eObj).getSubject() == aSubject ) eId = ((SubjectInstanceDescriptorImpl) eObj).getInstanceId();
}
}
return eId;
}
/**
* Closes the connection and disposes all proxy controls. This method is
* called whenever a regular close failed because of an exception.
*/
private void clearOnException() {
SubjectDescriptor eObjectDescriptor;
Iterator e = _instanceHandler.subjectDescriptors();
while ( e.hasNext() ) {
eObjectDescriptor = e.next();
onSubjectSignedOff( eObjectDescriptor.getSubject() );
}
_instanceHandler.clear();
}
/**
* Signs off all subjects.
*/
private void signOffAllSubjects() {
Iterator e = _instanceHandler.subjectDescriptors();
SubjectDescriptor eObjDescriptor;
while ( e.hasNext() )
try {
eObjDescriptor = e.next();
signOffSubject( eObjDescriptor );
}
catch ( OpenException oe ) {
LOGGER.error( "Catched a with message = " + oe.getMessage() );
if ( oe.getCause() instanceof IOException ) clearOnException();
}
_instanceHandler.clear();
}
/**
*/
private void closeOnException() {
clearOnException();
SubjectDescriptor eObjectDescriptor;
Iterator e = _instanceHandler.subjectDescriptors();
while ( e.hasNext() ) {
eObjectDescriptor = e.next();
onSubjectSignedOff( eObjectDescriptor.getSubject() );
}
}
/**
* A request from a {@link RemoteClient} is pushed to the
* {@link RemoteServer} using this method.
*
* @param aMethodRequestDescriptor An object of type BlueprintMethodRequest
* from which the request can be retrieved.
*
* @return Returns a BlueprintMethodReplyDescriptor object containing the
* reply-descriptor of the pushed method
*
* @exception OpenException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
private Reply pushMethodRequest( MethodRequest aMethodRequestDescriptor ) throws OpenException {
if ( aMethodRequestDescriptor == null ) { return null; }
SubjectDescriptor objDescriptor;
synchronized ( _instanceHandler ) {
objDescriptor = _instanceHandler.getSubjectDescriptor( aMethodRequestDescriptor.getInstanceId() );
}
if ( objDescriptor == null ) { return null; }
try {
MethodInvokationDaemon theDaemon = new MethodInvokationDaemon( objDescriptor.getSubject(), aMethodRequestDescriptor.getMethodName(), aMethodRequestDescriptor.getArgumentArray(), aMethodRequestDescriptor.getArgumentTypeArray() );
getExecutorService().execute( theDaemon );
RetryTimeout theRetryTimeout = new RetryTimeoutImpl( WAIT_FOR_REPLY_TIMEOUT / 5 * 4, WAIT_FOR_REPLY_LOOPS );
while ( (!theDaemon.hasResult()) && theRetryTimeout.hasNextRetry() && isOpened() ) {
theRetryTimeout.nextRetry( theDaemon );
}
if ( !theDaemon.hasResult() ) {
LOGGER.warn( "Some timeout has occurred - timeout is ignored..." );
}
Reply theMethodReplyDescriptor = new ReplyDescriptorImpl( theDaemon.getReturnValue(), theDaemon.getException(), aMethodRequestDescriptor );
return theMethodReplyDescriptor;
}
catch ( IllegalArgumentException iae ) {
throw new InvalidMethodRequestRuntimeException( iae );
}
catch ( NoSuchMethodException nsme ) {
throw new InvalidMethodRequestRuntimeException( nsme );
}
}
/**
* Signs off an instance previously published using the
* {@link #publishSubject(Object)} method. The toolkit uses the
* corresponding {@link #signOffSubject(Object))}.
*
* @param aInstanceDescriptor An object of type {@link InstanceId}
* containing the information needed to sign-off an instance.
* @return True is returned if the instanceDescriptor could be signed off,
* else false is returned
*/
private boolean serviceSignoffInstanceDescriptor( 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 instanceId = aInstanceDescriptor.getInstanceId();
if ( _instanceHandler.hasSignedOffInstanceId( instanceId ) ) return true;
// ---------------------------------------------------------------------
// Get the subject instance instead of testing for its existence in
// order to avoid thread race conditions:
// ---------------------------------------------------------------------
SubjectDescriptor theSubjectDescriptor = _instanceHandler.getSubjectDescriptor( instanceId );
// ---------------------------------------------------------------------
if ( theSubjectDescriptor == null ) {
RetryTimeout theRetryTimeout = new RetryTimeoutImpl( IoTimeout.MIN.getMilliseconds(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getNumber() );
while ( !isClosed() && theRetryTimeout.hasNextRetry() && theSubjectDescriptor == null ) {
theRetryTimeout.nextRetry();
theSubjectDescriptor = _instanceHandler.getSubjectDescriptor( instanceId );
}
if ( theSubjectDescriptor == null && !isClosed() ) {
LOGGER.warn( "Expected known instance ID, but instance ID <" + instanceId + "> is not known by the server (probably all instances have been signed off)." );
// throw new UnknownInstanceIdRuntimeException( "Expected a known instance ID in argument (probably all instances have been signed off), the unknown instance ID is <" + instanceId + ">." );
}
}
if ( theSubjectDescriptor != null ) onSubjectSignedOff( theSubjectDescriptor.getSubject() );
return true;
}
/**
* Signs off an instance previously published using the
* {@link #publishSubject(Object)} method.
*
* @param aInstanceDescriptor An object of type {@link InstanceId}
* containing the information needed to sign-off an instance.
*
* @param aTimeoutInMs If greater than 0 then the timeout that the subject
* is being signed-off even when a {@link VetoException} is thrown by
* the {@link RemoteClient}. In case the Timeout is 0 then the
* subject will be signed off immediately. In case the timeout is -1,
* then the {@link VetoException} (if any) is forwarded and the
* sign-off operation is aborted.
*
* @return True is returned if the {@link InstanceId} could be signed off,
* else false is returned
*
* @throws VetoException Thrown to signal that an operation is being vetoed
* by a third party observing the invocation of the given operation.
*
* @throws OpenException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
private boolean signoffInstanceDescriptor( InstanceId aInstanceDescriptor, int aTimeoutInMs ) throws OpenException, VetoException {
// synchronized(_instanceHandler.getMonitor()) {
if ( aInstanceDescriptor == null ) { return false; }
if ( aInstanceDescriptor.getInstanceId() == null ) { return false; }
String theInstanceId = aInstanceDescriptor.getInstanceId();
if ( !_instanceHandler.hasInstanceId( theInstanceId ) ) { return false; }
if ( !_instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) { return false; }
if ( _instanceHandler.hasSignedOffInstanceId( theInstanceId ) ) { throw new DuplicateInstanceIdRuntimeException( "The instance <" + theInstanceId + "> of the provided instance descriptorin is already in use and being used up." ); }
SignoffSubjectMessageImpl theSignoffSubjectJob = _signOffJobFactory.toInstance();
theSignoffSubjectJob.setInstanceId( aInstanceDescriptor.getInstanceId() );
theSignoffSubjectJob.setTimeoutInMs( aTimeoutInMs );
PublishSubjectReplyMessageImpl thePublishSubjectReplyJob = _publishSubjectReplyJobFactory.toInstance();
thePublishSubjectReplyJob.setInstanceId( theInstanceId );
thePublishSubjectReplyJob.setHasReply( false );
synchronized ( _instanceHandler ) {
_instanceHandler.addReplyDescriptor( thePublishSubjectReplyJob, theInstanceId );
}
toReceiver( theSignoffSubjectJob );
RetryTimeout theRetryTimeout = new RetryTimeoutImpl( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
while ( (!thePublishSubjectReplyJob.hasReply()) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( ENABLE_EXTENDED_DEBUG_LOGGING ) LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while waiting for method reply for <" + WAIT_FOR_REPLY_LOOPS + "> ms." );
theRetryTimeout.nextRetry( thePublishSubjectReplyJob );
}
// ---------------------------------------------------------------------
// Wait till the monitor is notified or timeout has run out of time:
// ---------------------------------------------------------------------
synchronized ( _instanceHandler ) {
_instanceHandler.removeReplyDescriptor( theInstanceId );
}
// ---------------------------------------------------------------------
if ( !thePublishSubjectReplyJob.hasReply() ) {
if ( ENABLE_OBJECT_POOLING ) _publishSubjectReplyJobFactory.recycleInstance( thePublishSubjectReplyJob );
// @formatter:off
// throw new OpenTimeoutException( WAIT_FOR_REPLY_TIMEOUT, "While processing the request a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot; probably lost the connection (you probably should close the connection)." );
// @formatter:on
// TODO: Throw actual OpenTimeoutException or similar!
throw new IllegalStateException( "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)." );
}
if ( thePublishSubjectReplyJob.isException() ) {
Throwable theException = thePublishSubjectReplyJob.getException();
if ( theException instanceof VetoException ) {
if ( aTimeoutInMs == 0 ) {
/* ignore, sign-off immediately */
}
else if ( aTimeoutInMs > 0 ) {
synchronized ( this ) {
try {
Thread.sleep( aTimeoutInMs );
}
catch ( InterruptedException ignored ) {}
}
}
else if ( aTimeoutInMs == -1 ) { throw (VetoException) theException; }
}
else {
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a subject." );
}
}
if ( thePublishSubjectReplyJob.isReturnValue() ) {
if ( thePublishSubjectReplyJob.getReturnValue() instanceof Boolean ) {
SubjectDescriptor theSubjectDescriptor = _instanceHandler.removeSubjectDescriptor( theInstanceId );
onSubjectSignedOff( theSubjectDescriptor.getSubject() );
return ((Boolean) thePublishSubjectReplyJob.getReturnValue()).booleanValue();
}
}
if ( ENABLE_OBJECT_POOLING ) {
_publishSubjectReplyJobFactory.recycleInstance( thePublishSubjectReplyJob );
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a subject." );
}
return false;
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
/**
* Helper class for providing synchronized access to vital data structures.
*/
private static class InstanceHandler implements BusyAccessor {
private Map _instanceIdsToSubjectDescriptors = new HashMap();
private Map _instanceIdToMethodReplyDescriptor = new HashMap();
private List _subjects = new ArrayList();
private Set _signedOffInstanceIds = Collections.newSetFromMap( new WeakHashMap() );
/**
* 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 instanceId to which the reply descriptor is to
* be associated.
* @return True if the operation has been performed successfully.
* @throws UnexpectedRuntimeException Description is currently not
* available!
*/
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." ); }
if ( hasSignedOffInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." ); }
}
else {
if ( hasMethodReplyDescriptor( aInstanceId ) ) { return false; }
if ( hasSignedOffInstanceId( aInstanceId ) ) { return false; }
}
_instanceIdToMethodReplyDescriptor.put( aInstanceId, aMethodReplyDescriptor );
return true;
}
/**
* Adds an instance id to the list of signed off instance id's.
*
* @param instanceId The instance id to be added
* @return True if the operation has been performed successfully.
*/
boolean addSignedOffInstanceId( String instanceId ) {
synchronized ( this ) {
return (_signedOffInstanceIds.add( instanceId ));
}
}
/**
* Clears the elements contained in this instance.
*/
void clear() {
clearSignedOffInstanceIds();
clearSubjectDescriptos();
clearReplyDescriptors();
}
/**
* Returns the list of published subject instances.
*
* @return The list of published subjects.
*/
List getSubjects() {
return _subjects;
}
/**
* 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.
*
* @throws UnexpectedRuntimeException Description is currently not
* available!
*/
Reply getMethodReplyDescriptor( 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." ); }
if ( hasSignedOffInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." ); }
}
else {
if ( !hasMethodReplyDescriptor( aInstanceId ) ) { return null; }
if ( hasSignedOffInstanceId( aInstanceId ) ) { return null; }
}
return _instanceIdToMethodReplyDescriptor.get( aInstanceId );
}
/**
* Returns true if the instance id is in use or was in use by this
* instance.
*
* @param instanceId The instance id to be tested.
* @return True if the given instance id is in use or has been in use.
*/
boolean hasInstanceId( String instanceId ) {
return ((_instanceIdsToSubjectDescriptors.containsKey( instanceId )) || _signedOffInstanceIds.contains( instanceId ));
}
/**
* Tests if the provided object is contained in any of the object
* descriptors managed by this instance.
*
* @param obj The object which's existence is to be tested.
* @return True if the object has been found.
*/
boolean hasSubject( Object obj ) {
return _subjects.contains( obj );
}
/**
* Tests if the given isntance id is in the list of the waiting reply
* descriptors.
*
* @param instanceId The instance id for which the existence of a reply
* descriptor is to be tested.
* @return True if there is a reply descriptor assosiated to the
* instance id.
*/
boolean hasMethodReplyDescriptor( String instanceId ) {
return _instanceIdToMethodReplyDescriptor.containsKey( instanceId );
}
/**
* Tests if the provided instance id has already been used and is not in
* use any more.
*
* @param instanceId Thje id which is to be tested.
* @return True is returned if the provided instance id has already been
* used, else false is returned
*/
boolean hasSignedOffInstanceId( String instanceId ) {
return (_signedOffInstanceIds.contains( instanceId ));
}
/**
* Returns an iterator containing all the instance id's currently in
* use.
*
* @return An iterator containing all the instance id's currently in
* use.
*/
Iterator instanceIds() {
return _instanceIdsToSubjectDescriptors.keySet().iterator();
}
/**
*
*
* @return Description is currently not available!
*/
boolean isEmpty() {
return _subjects.isEmpty();
}
/**
* Adds an object descriptor which is associated to the given instance
* id.
*
* @param theObjectDescriptor The object descriptor to be added.
*
* @param aInstanceId The instance id to which the object descriptor is
* to be associated.
*
* @return True if the operation has been performed successfully.
*
* @throws UnexpectedRuntimeException Description is currently not
* available!
*/
boolean addSubjectDescriptor( SubjectDescriptor theObjectDescriptor, String aInstanceId ) {
synchronized ( this ) {
if ( !_instanceIdsToSubjectDescriptors.containsKey( aInstanceId ) ) {
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( hasInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a object descriptor; an unused instance ID must be provided." ); }
if ( hasSubject( theObjectDescriptor.getSubject() ) ) { throw new IllegalArgumentException( "The subject to be related to instance ID <" + aInstanceId + "> has already been added." ); }
}
else {
if ( hasInstanceId( aInstanceId ) ) { return false; }
if ( hasSubject( (Object) theObjectDescriptor.getSubject() ) ) { return false; }
}
_instanceIdsToSubjectDescriptors.put( aInstanceId, theObjectDescriptor );
_subjects.add( theObjectDescriptor.getSubject() );
return true;
}
}
return false;
}
/**
* Returns an iterator of the objects descriptors.
*
* @return An object of type java.util.Iterator containing the objects
* found in the object descriptors.
*/
Iterator subjectDescriptors() {
synchronized ( this ) {
List returnList = new ArrayList( _instanceIdsToSubjectDescriptors.values() );
return returnList.iterator();
}
}
/**
* Returns true if an object descriptor is associated to the provided
* instance id.
*
* @param instanceId The instance id which's existence is to be tested.
* @return True if the instance id is used and an object descriptor
* associated to it.
*/
// boolean hasSubject( String instanceId ) {
// return (_instanceIdsToSubjectDescriptors.containsKey( instanceId ));
// }
/**
* Returns the object descriptor is associated to the provided instance
* id.
*
* @param aInstanceId The instance id of the object descriptor.
* @return The subject or null if there is none such object descriptor.
*/
SubjectDescriptor getSubjectDescriptor( String aInstanceId ) {
return (_instanceIdsToSubjectDescriptors.get( aInstanceId ));
}
/**
* Removes an object descriptor identified by an instance id.
*
* @param aInstanceId The instance id to which the object descriptor is
* associated which is to be removed.
* @return An object of type BlueprintObjectDescriptor being the object
* descriptor which has been removed.
* @throws UnexpectedRuntimeException Description is currently not
* available!
*/
SubjectDescriptor removeSubjectDescriptor( String aInstanceId ) {
synchronized ( this ) {
if ( _instanceIdsToSubjectDescriptors.containsKey( aInstanceId ) ) {
SubjectDescriptor theObjectDescriptor = _instanceIdsToSubjectDescriptors.remove( aInstanceId );
if ( PERFORM_CONSISTENCY_CHECKS ) {
if ( !_subjects.contains( theObjectDescriptor.getSubject() ) ) { throw new IllegalStateException( "The subject described by the object descriptor associated to the instance ID <" + aInstanceId + "> is unknown; there is an illage state regarding the subject and the object descriptor data structures." ); }
if ( hasSignedOffInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." ); }
}
else {
if ( !_subjects.contains( theObjectDescriptor.getSubject() ) ) { return null; }
if ( hasSignedOffInstanceId( aInstanceId ) ) { return null; }
}
_subjects.remove( theObjectDescriptor.getSubject() );
addSignedOffInstanceId( aInstanceId );
return theObjectDescriptor;
}
}
return null;
}
/**
* Removes all object descriptors from this instance.
*/
private void clearSubjectDescriptos() {
synchronized ( this ) {
_instanceIdsToSubjectDescriptors.clear();
_subjects.clear();
}
}
/**
* Removes the reply descriptor from this instance which has been
* associated to the given instance id.
*
* @param aInstanceId The instanceId which's reply descriptor is to be
* removed.
* @return The reply descriptor which has been removed.
* @throws UnexpectedRuntimeException Description is currently not
* available!
*/
Reply removeReplyDescriptor( 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." ); }
if ( hasSignedOffInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." ); }
}
else {
if ( !hasMethodReplyDescriptor( aInstanceId ) ) { return null; }
if ( hasSignedOffInstanceId( aInstanceId ) ) { return null; }
}
return _instanceIdToMethodReplyDescriptor.remove( aInstanceId );
}
/**
*
*
* @return Description is currently not available!
*/
int size() {
return _subjects.size();
}
/**
* Clears the list of reply descriptors.
*/
private void clearReplyDescriptors() {
_instanceIdToMethodReplyDescriptor.clear();
}
/**
* Removes all instance id's from the list of signed-off instance IDs.
*/
private void clearSignedOffInstanceIds() {
synchronized ( this ) {
_signedOffInstanceIds.clear();
}
}
@Override
synchronized public boolean isBusy() {
return (!_instanceIdToMethodReplyDescriptor.isEmpty());
}
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// METHOD INVOCATION DAEMON:
// /////////////////////////////////////////////////////////////////////////
/**
* Daemon handling inter-process method invocation calls.
*/
private class MethodInvokationDaemon implements Runnable {
// /////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////
private Object[] _argumentArray;
private boolean _hasResult = false;
private Method _method;
private Object _providedObject;
private Throwable _returnThrowable = null;
private Object _returnValue = null;
// /////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////
/**
* Constructs the {@link MethodInvokationDaemon} with the given
* attributes identifying that method.
*
* @param aSubject The subject upon which to invoke the method.
*
* @param aMethodName The name of the method to be invoked.
*
* @param anArgumentArray The arguments (instances) to be passed to the
* method to be invoked.
*
* @param aParameterArray The types of the parameters if the method to
* be invoked.
*/
MethodInvokationDaemon( Object aSubject, String aMethodName, Object[] anArgumentArray, Class>[] aParameterArray ) throws IllegalArgumentException, NoSuchMethodException {
_method = aSubject.getClass().getMethod( aMethodName, aParameterArray );
_providedObject = aSubject;
_argumentArray = anArgumentArray;
if ( _argumentArray != null ) for ( int i = 0; i < _argumentArray.length; i++ ) {
_argumentArray[i] = SerializeUtility.toSerializable( _argumentArray[i] );
}
}
@Override
public void run() {
try {
Object returnValue = _method.invoke( _providedObject, _argumentArray );
_returnValue = SerializeUtility.toSerializable( returnValue );
}
catch ( InvocationTargetException aInvocationTargetException ) {
LOGGER.warn( ExceptionUtility.toMessage( aInvocationTargetException ), aInvocationTargetException );
_returnThrowable = aInvocationTargetException.getTargetException();
}
catch ( IllegalAccessException aIllegalAccessException ) {
LOGGER.warn( ExceptionUtility.toMessage( aIllegalAccessException ), aIllegalAccessException );
throw new InvalidMethodRequestRuntimeException( aIllegalAccessException );
}
catch ( Throwable aThrowable ) {
LOGGER.warn( ExceptionUtility.toMessage( aThrowable ), aThrowable );
_returnThrowable = aThrowable;
}
finally {
_hasResult = true;
synchronized ( this ) {
notifyAll();
}
}
}
/**
*
*
* @return Description is currently not available!
*/
Object getReturnValue() {
return _returnValue;
}
/**
* @see ExceptionAccessor
*/
Throwable getException() {
return _returnThrowable;
}
/**
*
*
* @return Description is currently not available!
*/
boolean hasResult() {
return _hasResult;
}
}
// /////////////////////////////////////////////////////////////////////////
// METHOD REPLY JOB FACTORY:
// /////////////////////////////////////////////////////////////////////////
/**
* Implementation of the {@link RecyclableTypeFactory} creating instances of
* the according type.
*/
protected static class MethodReplyJobFactoryImpl extends AbstractRecyclableTypeFactory {
@Override
protected MethodReplyMessageImpl newInstance() {
return new MethodReplyMessageImpl();
}
@Override
protected MethodReplyMessageImpl newInstance( Properties aProperties ) {
return new MethodReplyMessageImpl();
}
}
// /////////////////////////////////////////////////////////////////////////
// PUBLISH PROXY JOB FACTORY:
// /////////////////////////////////////////////////////////////////////////
/**
* Implementation of the {@link RecyclableTypeFactory} creating instances of
* the according type.
*/
protected static class PublishProxyJobFactoryImpl extends AbstractRecyclableTypeFactory {
@Override
protected PublishSubjectMessageImpl newInstance() {
return new PublishSubjectMessageImpl();
}
@Override
protected PublishSubjectMessageImpl newInstance( Properties aProperties ) {
return new PublishSubjectMessageImpl();
}
}
// /////////////////////////////////////////////////////////////////////////
// SIGN-OFF JOB FACTORY:
// /////////////////////////////////////////////////////////////////////////
/**
* Implementation of the {@link RecyclableTypeFactory} creating instances of
* the according type.
*/
protected static class SignOffJobFactoryImpl extends AbstractRecyclableTypeFactory {
@Override
protected SignoffSubjectMessageImpl newInstance() {
return new SignoffSubjectMessageImpl();
}
@Override
protected SignoffSubjectMessageImpl newInstance( Properties aProperties ) {
return new SignoffSubjectMessageImpl();
}
}
}