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.RemoteServer Maven / Gradle / Ivy
Go to download
Artifact with proxy functionality for plain remote procedure calls
(RPC), a POJO alternative to RMI.
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.remoting;
import java.io.IOException;
import java.lang.reflect.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 java.util.logging.Level;
import java.util.logging.Logger;
import org.refcodes.component.DigestException;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryTimeout;
import org.refcodes.data.IoTimeout;
import org.refcodes.data.RetryLoopCount;
import org.refcodes.data.SleepLoopTime;
import org.refcodes.exception.ExceptionAccessor;
import org.refcodes.exception.Trap;
import org.refcodes.exception.VetoException;
import org.refcodes.exception.VetoException.VetoRuntimeException;
import org.refcodes.generator.Generator;
import org.refcodes.generator.UniqueIdGenerator;
import org.refcodes.mixin.BusyAccessor;
/**
* A {@link RemoteServer} promotes subjects to be operated on by
* {@link RemoteClient} instances.
*/
public class RemoteServer extends AbstractRemote {
// /////////////////////////////////////////////////////////////////////////
// STATICS:
// /////////////////////////////////////////////////////////////////////////
private static final Logger LOGGER = Logger.getLogger( RemoteServer.class.getName() );
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private InstanceHandler _instanceHandler = new InstanceHandler();
private Generator _instanceIdGenerator = new UniqueIdGenerator( INSTANCE_ID_LENGTH );
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
/**
* Default constructor of the {@link RemoteServer} will use a default
* {@link ExecutorService}.
*/
public RemoteServer() {
super( null );
}
/**
* Instantiates a new {@link RemoteServer} instance with the given
* {@link ExecutorService} to be used.
*
* @param aExecutorService The {@link ExecutorService} to be used.
*/
public RemoteServer( ExecutorService aExecutorService ) {
super( aExecutorService );
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public void clear() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
signOffAllSubjects();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void close() throws IOException {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "CLOSE called on <" + getClass().getName() + ">." );
}
close( null );
}
/**
* {@inheritDoc}
*/
@Override
public boolean isBusy() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isBusy();
}
/**
* Returns true if the provided subject is contained inside this
* {@link RemoteServer}.
*
* @param aSubject The subject to be tested if it is contained inside the
* {@link RemoteServer}.
*
* @return True if the given subject is contained inside the
* {@link RemoteServer}.
*/
public boolean hasSubject( Object aSubject ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.hasSubject( aSubject );
}
/**
* Returns an (immutable) iterator containing all the proxy objects
* previously being published. Use the {@link #signOffSubject(Object)}
* method in order to remove a published subject.
*
* @return An iterator containing the published proxy objects.
*/
public Iterator subjects() {
synchronized ( _instanceHandler ) {
final List thjeList = new ArrayList<>( _instanceHandler.getSubjects() );
return thjeList.iterator();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.isEmpty();
}
/**
* Publishes an object to any {@link RemoteClient} connected to the
* {@link RemoteServer}.
*
* @param aSubject A subject being published for inter-process communication
* such as remote procedure calls or remote method invocations.
*
* @return True is returned if the subject could be published, else false is
* returned
*
* @throws IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
public boolean publishSubject( Object aSubject ) throws IOException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
synchronized ( _instanceHandler ) {
SubjectDescriptor eObjDescriptor;
final 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." );
}
final SubjectInstanceDescriptor theSubjectInstanceDescriptor = new SubjectInstanceDescriptor( aSubject, theInstanceId );
final ClassDescriptor classDescriptor = new ClassDescriptor( aSubject.getClass(), theInstanceId );
if ( _instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) {
throw new DuplicateInstanceIdRuntimeException( "The instance ID <" + theInstanceId + "> is already in use by the internal instance handler." );
}
final PublishSubjectMessage thePublishSubjectJob = new PublishSubjectMessage();
thePublishSubjectJob.setClassDescriptor( classDescriptor );
final PublishSubjectReplyMessage theMethodReplyRemotingJob = new PublishSubjectReplyMessage();
theMethodReplyRemotingJob.setInstanceId( theInstanceId );
theMethodReplyRemotingJob.setHasReply( false );
_instanceHandler.addReplyDescriptor( theMethodReplyRemotingJob, theInstanceId );
try {
toReceiver( thePublishSubjectJob );
}
catch ( IOException aException ) {
LOGGER.log( Level.WARNING, Trap.asMessage( aException ), aException );
_instanceHandler.removeReplyDescriptor( theInstanceId );
if ( aException.getCause() instanceof IOException ) {
closeOnException();
}
throw aException;
}
final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
while ( ( !theMethodReplyRemotingJob.hasReply() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( IS_LOG_DEBUG_ENABLED ) {
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() ) {
throw new IllegalStateException( "While processing the request for session <" + theMethodReplyRemotingJob.getInstanceId() + "> of instance <" + theMethodReplyRemotingJob.getInstanceId() + "> (" + theMethodReplyRemotingJob.getClass().getSimpleName() + ") a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot (propably lost the connection)." );
}
if ( theMethodReplyRemotingJob.isException() ) {
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descriptor. Sorry - operation aborted!" );
}
if ( theMethodReplyRemotingJob.isReturnValue() ) {
if ( theMethodReplyRemotingJob.getReturnValue() instanceof Boolean ) {
final boolean theReturnValue = ( (Boolean) theMethodReplyRemotingJob.getReturnValue() ).booleanValue();
if ( theReturnValue ) {
_instanceHandler.addSubjectDescriptor( theSubjectInstanceDescriptor, theInstanceId );
onSubjectPublished( theSubjectInstanceDescriptor.getSubject() );
}
return theReturnValue;
}
}
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descriptor. Sorry - operation aborted!" );
}
return false;
}
}
/**
* Tries to sign off the (previously published) subject, this can be vetoed
* in case the subject is still in use by a {@link RemoteClient}.
*
* @param aSubject Description is currently not available!
*
* @return True if the removal of the subject has been successful. If the
* subject has not been found then false is returned. If a
* {@link RemoteClient} threw a {@link VetoException} then the
* sign-off is aborted.
*
* @throws VetoException the veto exception
* @throws IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
public boolean signOffSubject( Object aSubject ) throws IOException, VetoException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
final String theSubjectId = toSubjectId( aSubject );
if ( theSubjectId == null ) {
return false;
}
return signoffInstanceDescriptor( new InstanceDescriptor( theSubjectId ), -1 );
}
/**
* Signs off the (previously published) subject, this be vetoed even in case
* the subject is still in use by a {@link RemoteClient}, but the veto will
* only delay the sign off by the given timeout.
*
* @param aSubject The subject to be signed off.
* @param aTimeoutMillis The timeout to be granted in case the sign-off has
* been vetoed, nevertheless the subject will be signed off after the
* timeout elapsed.
*
* @return True if the removal of the subject has been successful. If the
* subject has not been found then false is returned. If a
* {@link RemoteClient} threw a {@link VetoException} then the
* sign-off is aborted.
*
* @throws IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
public boolean signOffSubject( Object aSubject, int aTimeoutMillis ) throws IOException {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
final String theSubjectId = toSubjectId( aSubject );
if ( theSubjectId == null ) {
return false;
}
try {
return signoffInstanceDescriptor( new InstanceDescriptor( theSubjectId ), aTimeoutMillis );
}
catch ( VetoException aException ) {
throw new VetoRuntimeException( aException.getMessage(), aException );
}
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
return _instanceHandler.size();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void destroy() {
if ( !isDestroyed() ) {
super.destroy();
_instanceHandler.clear();
_instanceHandler = 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 IOException Thrown in case the operation failed due to an I/O
* malfunction such as the input- or output-connection (of an input-
* and output-connection pair) is not available or being
* disconnected.
*/
protected void doSendJob( Message aJob ) throws IOException {
toReceiver( aJob );
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public void digest( Message aJob ) throws DigestException {
try {
if ( aJob == null ) {
return;
}
// -----------------------------------------------------------------
// CLOSE CONNECTION JOB:
// -----------------------------------------------------------------
if ( aJob instanceof CloseConnectionMessage ) {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Received a close connection job to <" + getClass().getName() + ">; closing connection." );
}
close( (CloseConnectionMessage) aJob );
}
// -----------------------------------------------------------------
// PUBLISH SUBJECT REPLY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof PublishSubjectReplyMessage theReplyRemotingJob ) {
if ( theReplyRemotingJob.getInstanceId() == null ) {
return;
}
final 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..." );
}
final Object tmpReply = _instanceHandler.getMethodReplyDescriptor( theInstanceId );
if ( !( tmpReply instanceof PublishSubjectReplyMessage ) ) {
throw new InvalidMethodReplyRuntimeException( "Excpected a <" + PublishSubjectReplyMessage.class.toString() + "> to put the reply in." );
}
final PublishSubjectReplyMessage thePublishSubjectReplyJob = (PublishSubjectReplyMessage) tmpReply;
thePublishSubjectReplyJob.setReply( theReplyRemotingJob );
synchronized ( thePublishSubjectReplyJob ) {
thePublishSubjectReplyJob.notifyAll();
}
}
// -----------------------------------------------------------------
// METHOD REQUEST JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof MethodRequestMessage ) {
final MethodRequest theMethodRequestDescriptor = (MethodRequest) aJob;
final Reply theMethodReplyDescriptor = pushMethodRequest( theMethodRequestDescriptor );
if ( theMethodReplyDescriptor == null ) {
return;
}
final MethodReplyMessage theMethodReplyJob = new MethodReplyMessage();
theMethodReplyJob.setMethodReplyDescriptor( theMethodReplyDescriptor );
toReceiver( theMethodReplyJob );
}
// -----------------------------------------------------------------
// SIGN-OFF PROXY JOB:
// -----------------------------------------------------------------
else if ( aJob instanceof SignOffProxyMessage ) {
final InstanceId instanceDescriptor = aJob;
final boolean theReturnValue = serviceSignoffInstanceDescriptor( instanceDescriptor );
final CancelMethodReplyMessage theCancelReplyRemotingJob = new CancelMethodReplyMessage();
theCancelReplyRemotingJob.setInstanceId( instanceDescriptor.getInstanceId() );
theCancelReplyRemotingJob.setException( null );
theCancelReplyRemotingJob.setReturnValue( theReturnValue );
theCancelReplyRemotingJob.setHasReply( true );
try {
toReceiver( theCancelReplyRemotingJob );
}
catch ( IOException aException ) { /* ignore */ }
}
}
catch ( IOException aException ) {
throw new DigestException( aException );
}
}
/**
* {@inheritDoc}
*/
@Override
protected synchronized void close( CloseConnectionMessage aJob ) {
ControlFlowUtility.throwIllegalStateException( isDestroyed() );
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "CLOSE called on <" + getClass().getName() + "> with job <" + aJob + ">." );
}
if ( !isClosed() ) {
super.close( aJob );
final RetryTimeout theRetryTimeout = new RetryTimeout( IoTimeout.NORM.getTimeMillis(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getValue() );
while ( ( isBusy() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( IS_LOG_DEBUG_ENABLED ) {
LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while being BUSY for <" + SleepLoopTime.NORM.getTimeMillis() + "> ms." );
}
theRetryTimeout.nextRetry();
}
if ( isBusy() ) {
LOGGER.log( Level.WARNING, "Still being BUSY even after reaching the timeout of <" + IoTimeout.NORM.getTimeMillis() + "> ms, closing connection nonetheless." );
}
signOffAllSubjects();
try {
super.close();
}
catch ( IOException e ) {
LOGGER.log( Level.WARNING, "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 IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
private boolean signOffSubject( SubjectDescriptor aSubject ) throws IOException {
if ( isClosed() ) {
return false;
}
String eId = null;
final 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 InstanceDescriptor( 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;
final Iterator e = subjects();
while ( e.hasNext() ) {
final Object eObj = e.next();
if ( eObj == aSubject ) {
returnValue = true;
}
}
if ( returnValue == false ) {
return null;
}
final Iterator ee = subjects();
while ( ee.hasNext() ) {
final Object eObj = ee.next();
if ( eObj instanceof SubjectInstanceDescriptor ) {
if ( ( (SubjectInstanceDescriptor) eObj ).getSubject() == aSubject ) {
eId = ( (SubjectInstanceDescriptor) 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;
final Iterator e = _instanceHandler.subjectDescriptors();
while ( e.hasNext() ) {
eObjectDescriptor = e.next();
onSubjectSignedOff( eObjectDescriptor.getSubject() );
}
_instanceHandler.clear();
}
/**
* Signs off all subjects.
*/
private void signOffAllSubjects() {
final Iterator e = _instanceHandler.subjectDescriptors();
SubjectDescriptor eObjDescriptor;
while ( e.hasNext() ) {
try {
eObjDescriptor = e.next();
signOffSubject( eObjDescriptor );
}
catch ( IOException ioe ) {
LOGGER.log( Level.SEVERE, "Cannot sign off all subjects as of: " + Trap.asMessage( ioe ), ioe );
if ( ioe.getCause() instanceof IOException ) {
clearOnException();
}
}
}
_instanceHandler.clear();
}
/**
* Close on exception.
*/
private void closeOnException() {
clearOnException();
SubjectDescriptor eObjectDescriptor;
final 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 IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
*/
private Reply pushMethodRequest( MethodRequest aMethodRequestDescriptor ) throws IOException {
if ( aMethodRequestDescriptor == null ) {
return null;
}
final SubjectDescriptor objDescriptor;
synchronized ( _instanceHandler ) {
objDescriptor = _instanceHandler.getSubjectDescriptor( aMethodRequestDescriptor.getInstanceId() );
}
if ( objDescriptor == null ) {
return null;
}
try {
final MethodInvokationDaemon theDaemon = new MethodInvokationDaemon( objDescriptor.getSubject(), aMethodRequestDescriptor.getMethodName(), aMethodRequestDescriptor.getArgumentArray(), aMethodRequestDescriptor.getArgumentTypeArray() );
getExecutorService().execute( theDaemon );
final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_REPLY_TIMEOUT / 5 * 4, WAIT_FOR_REPLY_LOOPS );
while ( ( !theDaemon.hasResult() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
theRetryTimeout.nextRetry( theDaemon );
}
if ( !theDaemon.hasResult() ) {
LOGGER.log( Level.WARNING, "Some timeout has occurred - timeout is ignored..." );
}
final Reply theMethodReplyDescriptor = new ReplyDescriptor( theDaemon.getReturnValue(), theDaemon.getException(), aMethodRequestDescriptor );
return theMethodReplyDescriptor;
}
catch ( NoSuchMethodException | IllegalArgumentException 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 ." );
}
final 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 ) {
final RetryTimeout theRetryTimeout = new RetryTimeout( IoTimeout.MIN.getTimeMillis(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getValue() );
while ( !isClosed() && theRetryTimeout.hasNextRetry() && theSubjectDescriptor == null ) {
theRetryTimeout.nextRetry();
theSubjectDescriptor = _instanceHandler.getSubjectDescriptor( instanceId );
}
if ( theSubjectDescriptor == null && !isClosed() ) {
LOGGER.log( Level.WARNING, "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 aTimeoutMillis 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 IOException Thrown in case opening or accessing an open line
* (connection, junction, link) caused problems.
* @throws VetoException Thrown to signal that an operation is being vetoed
* by a third party observing the invocation of the given operation.
*/
private boolean signoffInstanceDescriptor( InstanceId aInstanceDescriptor, int aTimeoutMillis ) throws IOException, VetoException {
// synchronized(_instanceHandler.getMonitor()) {
if ( aInstanceDescriptor == null ) {
return false;
}
if ( aInstanceDescriptor.getInstanceId() == null ) {
return false;
}
final 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." );
}
final SignOffSubjectMessage theSignoffSubjectJob = new SignOffSubjectMessage();
theSignoffSubjectJob.setInstanceId( aInstanceDescriptor.getInstanceId() );
theSignoffSubjectJob.setReadTimeoutMillis( aTimeoutMillis );
final PublishSubjectReplyMessage thePublishSubjectReplyJob = new PublishSubjectReplyMessage();
thePublishSubjectReplyJob.setInstanceId( theInstanceId );
thePublishSubjectReplyJob.setHasReply( false );
synchronized ( _instanceHandler ) {
_instanceHandler.addReplyDescriptor( thePublishSubjectReplyJob, theInstanceId );
}
toReceiver( theSignoffSubjectJob );
final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
while ( ( !thePublishSubjectReplyJob.hasReply() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
if ( IS_LOG_DEBUG_ENABLED ) {
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() ) {
// @formatter:off
// throw new IOTimeoutException( 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
throw new IllegalStateException( "While processing the request for session <" + thePublishSubjectReplyJob.getInstanceId() + "> of instance <" + thePublishSubjectReplyJob.getInstanceId() + "> (" + thePublishSubjectReplyJob.getClass().getSimpleName() + ") a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot (propably lost the connection)." );
}
if ( thePublishSubjectReplyJob.isException() ) {
final Throwable theException = thePublishSubjectReplyJob.getException();
if ( theException instanceof VetoException ) {
if ( aTimeoutMillis == 0 ) {
/* ignore, sign-off immediately */
}
else if ( aTimeoutMillis > 0 ) {
synchronized ( this ) {
try {
Thread.sleep( aTimeoutMillis );
}
catch ( InterruptedException ignored ) {}
}
}
else if ( aTimeoutMillis == -1 ) {
throw (VetoException) theException;
}
}
else {
throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a subject." );
}
}
if ( thePublishSubjectReplyJob.isReturnValue() ) {
if ( thePublishSubjectReplyJob.getReturnValue() instanceof Boolean ) {
final SubjectDescriptor theSubjectDescriptor = _instanceHandler.removeSubjectDescriptor( theInstanceId );
onSubjectSignedOff( theSubjectDescriptor.getSubject() );
return ( (Boolean) thePublishSubjectReplyJob.getReturnValue() ).booleanValue();
}
}
return false;
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
/**
* Helper class for providing synchronized access to vital data structures.
*/
private static class InstanceHandler implements BusyAccessor {
private final Map _instanceIdsToSubjectDescriptors = new HashMap<>();
private final Map _instanceIdToMethodReplyDescriptor = new HashMap<>();
private final List _subjects = new ArrayList<>();
private final 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.
*/
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.
*/
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();
}
/**
* Checks if is empty.
*
* @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.
*/
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( 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 ) {
final List returnList = new ArrayList<>( _instanceIdsToSubjectDescriptors.values() );
return returnList.iterator();
}
}
/**
* Returns true if an object descriptor is associated to the provided
* instance id.
*
* @param aInstanceId the instance id
*
* @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.
*/
SubjectDescriptor removeSubjectDescriptor( String aInstanceId ) {
synchronized ( this ) {
if ( _instanceIdsToSubjectDescriptors.containsKey( aInstanceId ) ) {
final 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.
*/
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 );
}
/**
* Size.
*
* @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();
}
}
/**
* Checks if is busy.
*
* @return true, if is busy
*/
@Override
public synchronized boolean isBusy() {
return ( !_instanceIdToMethodReplyDescriptor.isEmpty() );
}
}
// /////////////////////////////////////////////////////////////////////////
// METHOD INVOCATION DAEMON:
// /////////////////////////////////////////////////////////////////////////
/**
* Daemon handling inter-process method invocation calls.
*/
private class MethodInvokationDaemon implements Runnable {
// /////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////
private final Object[] _argumentArray;
private boolean _hasResult = false;
private final Method _method;
private final 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.
*
* @throws IllegalArgumentException the illegal argument exception
* @throws NoSuchMethodException the no such method exception
*/
MethodInvokationDaemon( Object aSubject, String aMethodName, Object[] anArgumentArray, Class>[] aParameterArray ) throws 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] );
}
}
}
/**
* Run.
*/
@Override
public void run() {
try {
final Object returnValue = _method.invoke( _providedObject, _argumentArray );
_returnValue = SerializeUtility.toSerializable( returnValue );
}
catch ( InvocationTargetException aInvocationTargetException ) {
LOGGER.log( Level.WARNING, Trap.asMessage( aInvocationTargetException ), aInvocationTargetException );
_returnThrowable = aInvocationTargetException.getTargetException();
}
catch ( IllegalAccessException aIllegalAccessException ) {
LOGGER.log( Level.WARNING, Trap.asMessage( aIllegalAccessException ), aIllegalAccessException );
throw new InvalidMethodRequestRuntimeException( aIllegalAccessException );
}
catch ( Throwable aThrowable ) {
LOGGER.log( Level.WARNING, Trap.asMessage( aThrowable ), aThrowable );
_returnThrowable = aThrowable;
}
finally {
_hasResult = true;
synchronized ( this ) {
notifyAll();
}
}
}
/**
* Gets the return value.
*
* @return Description is currently not available!
*/
Object getReturnValue() {
return _returnValue;
}
/**
* Gets the exception.
*
* @return the exception
*
* @see ExceptionAccessor
*/
Throwable getException() {
return _returnThrowable;
}
/**
* Checks for result.
*
* @return Description is currently not available!
*/
boolean hasResult() {
return _hasResult;
}
}
}