/*
* Copyright (c) 2004-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.endpoint;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Timer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.endpoint.AbstractMessenger;
import net.jxta.endpoint.ChannelMessenger;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.Messenger;
import net.jxta.endpoint.MessengerState;
import net.jxta.endpoint.MessengerStateListener;
import net.jxta.endpoint.OutgoingMessageEvent;
import net.jxta.impl.util.threads.SelfCancellingTask;
import net.jxta.impl.util.threads.TaskManager;
import net.jxta.logging.Logging;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.util.SimpleSelectable;
/**
* This class is a near-drop-in replacement for the previous BlockingMessenger class.
* To subclassers (that is, currently, transports) the only difference is that some
* overloaded methods have a different name (class hierarchy reasons made it impossible
* to preserve the names without forcing an API change for applications).
*
* The other difference which is not API visible, is that it implements the
* standard MessengerState behaviour and semantics required by the changes in the endpoint framework.
*
* This the only base messenger class meant to be extended by outside code that is in the impl tree. The
* reason being that what it replaces was there already and that new code should not become dependant upon it.
*/
public abstract class BlockingMessenger extends AbstractMessenger {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(BlockingMessenger.class.getName());
/**
* The self destruct timer.
* <p/>
* When a messenger has become idle, it is closed. As a side effect, it
* makes the owning canonical messenger, if any, subject to removal if it is
* otherwise unreferenced.
*/
private final static transient Timer timer = new Timer("BlockingMessenger self destruct timer", true);
/*
* Actions that we defer to after returning from event methods. In other
* words, they cannot be done with the lock held, or they require calling
* more event methods. Because this messenger can take only one message at
* a time (saturated while sending), actions do not cascade much. Start can
* lead to connect if the sending fails, but, because we always fail to
* connect, connect will not lead to start. As a result we can get away with
* performing deferred actions recursively. That simplifies the code.
*/
private enum DeferredAction {
/**
* No deferred action.
*/
ACTION_NONE,
/**
* Must send message.
*/
ACTION_SEND,
/**
* Must report failure to connect.
*/
ACTION_CONNECT
}
/**
* The outstanding message.
*/
private Message currentMessage = null;
/**
* The serviceName override for that message.
*/
private String currentService = null;
/**
* The serviceParam override for that message.
*/
private String currentParam = null;
/**
* The exception that caused that message to not be sent.
*/
private Throwable currentThrowable = null;
/**
* true if we have deliberately closed our one message input queue.
*/
private boolean inputClosed = false;
/**
* Need to know which group this transport lives in, so that we can suppress
* channel redirection when in the same group. This is currently the norm.
*/
private final PeerGroupID homeGroupID;
/**
* The current deferred action.
*/
private DeferredAction deferredAction = DeferredAction.ACTION_NONE;
/**
* Reference to owning object. This is there so that the owning object is not subject to garbage collection
* unless this object here becomes itself unreferenced. That happens when the self destruct timer closed it.
*/
private Object owner = null;
/**
* The timer task watching over our self destruction requirement.
*/
private final ScheduledFuture<?> selfDestructTaskHandle;
/**
* State lock and engine.
*/
private final BlockingMessengerState stateMachine;
/**
* legacy artefact: transports need to believe the messenger is not yet closed in order to actually close it.
* So we lie to them just while we run their closeImpl method so that they do not see that the messenger is
* officially closed.
*/
private boolean lieToOldTransports = false;
/**
* Our statemachine implementation; just connects the standard AbstractMessengerState action methods to
* this object.
*/
private class BlockingMessengerState extends MessengerState {
protected BlockingMessengerState(MessengerStateListener listener) {
super(true, listener);
}
/*
* The required action methods.
*/
/**
* {@inheritDoc}
*/
@Override
protected void connectAction() {
deferredAction = DeferredAction.ACTION_CONNECT;
}
/**
* {@inheritDoc}
*/
@Override
protected void startAction() {
deferredAction = DeferredAction.ACTION_SEND;
}
/**
* {@inheritDoc}
*/
@Override
protected void closeInputAction() {
// we're synchonized here. (invoked from stateMachine).
inputClosed = true;
}
/**
* {@inheritDoc}
*/
@Override
protected void closeOutputAction() {
// This will break the cnx; thereby causing a down event if we have a send in progress.
// If the cnx does not break before the current message is sent, then the message will be sent successfully,
// resulting in an idle event. Either of these events is enough to complete the shutdown process.
lieToOldTransports = true;
closeImpl();
lieToOldTransports = false;
// Disconnect from the timer.
if (selfDestructTaskHandle != null) {
selfDestructTaskHandle.cancel(false);
}
}
// This is a synchronous action. No synchronization needed: we're already synchronized, here.
// There's a subtlety here: we do not clear the current message. We let sendMessageB or sendMessageN
// deal with it, so that they can handle the status reporting each in their own way. So basically, all we
// do is to set a reason for that message to fail in case we are shutdown from the outside and that message
// is not sent yet. As long as there is a current message, it is guaranteed that there is a thread
// in charge of reporting its status. It is also guaranteed that when failAll is called, the input is
// already closed, and so, we have no obligation of making room for future messages immediately.
// All this aggravation is so that we do not have to create one context wrapper for each message just so
// that we can associate it with its result. Instead we use our single msg and single status model
// throughout.
@Override
protected void failAllAction() {
if (currentMessage == null) {
return;
}
if (currentThrowable == null) {
currentThrowable = new IOException("Messenger unexpectedly closed");
}
}
}
/**
* The implementation of channel messenger that getChannelMessenger returns:
* All it does is address rewriting. Even close() is forwarded to the shared messenger.
* The reason is that BlockingMessengers are not really shared; they're transitional
* entities used directly by CanonicalMessenger. GetChannel is used only to provide address
* rewriting when we pass a blocking messenger directly to incoming messenger listeners...this
* practice is to be removed in the future, in favor of making incoming messengers full-featured
* async messengers that can be shared.
*/
private final class BlockingMessengerChannel extends ChannelMessenger {
public BlockingMessengerChannel(EndpointAddress baseAddress, PeerGroupID redirection, String origService, String origServiceParam) {
super(baseAddress, redirection, origService, origServiceParam);
}
/**
* {@inheritDoc}
*/
public int getState() {
return BlockingMessenger.this.getState();
}
/**
* {@inheritDoc}
*/
public void resolve() {
BlockingMessenger.this.resolve();
}
/**
* {@inheritDoc}
*/
public void close() {
BlockingMessenger.this.close();
}
/**
* {@inheritDoc}
*
* <p/>
* Address rewriting done here.
*/
public boolean sendMessageN(Message msg, String service, String serviceParam) {
return BlockingMessenger.this.sendMessageN(msg, effectiveService(service), effectiveParam(service, serviceParam));
}
/**
* {@inheritDoc}
*
* <p/>
* Address rewriting done here.
*/
public void sendMessageB(Message msg, String service, String serviceParam) throws IOException {
BlockingMessenger.this.sendMessageB(msg, effectiveService(service), effectiveParam(service, serviceParam));
}
/**
* {@inheritDoc}
*
* <p/>
* We're supposed to return the complete destination, including
* service and param specific to that channel. It is not clear, whether
* this should include the cross-group mangling, though. For now, let's
* say it does not.
*/
public EndpointAddress getLogicalDestinationAddress() {
EndpointAddress rawLogical = getLogicalDestinationImpl();
if (rawLogical == null) {
return null;
}
return new EndpointAddress(rawLogical, origService, origServiceParam);
}
// Check if it is worth staying registered
public void itemChanged(Object changedObject) {
if (!notifyChange()) {
if (haveListeners()) {
return;
}
BlockingMessenger.this.unregisterListener(this);
if (!haveListeners()) {
return;
}
// Ooops collision. We should not have unregistered. Next time, then. In case of collision, the end result
// is always to stay registered. There's no harm in staying registered.
BlockingMessenger.this.registerListener(this);
}
}
/**
* {@inheritDoc}
* <p/>
* Always make sure we're registered with the shared messenger.
*/
@Override
protected void registerListener(SimpleSelectable l) {
BlockingMessenger.this.registerListener(this);
super.registerListener(l);
}
}
private void storeCurrent(Message msg, String service, String param) {
currentMessage = msg;
currentService = service;
currentParam = param;
currentThrowable = null;
}
/**
* Constructor.
* <p/>
* We start in the CONNECTED state, we pretend to have a queue of size 1, and we can never re-connect. Although this
* messenger fully respects the asynchronous semantics, it is saturated as soon as one msg is being send, and if not
* saturated, send is actually performed by the invoker thread. So return is not completely immediate. This is a barely
* acceptable implementation, but this is also a transition adapter that is bound to disappear one release from now. The main
* goal is to get things going until transports are adapted.
*
* @param homeGroupID the group that this messenger works for. This is the group of the endpoint service or transport
* that created this messenger.
* @param dest where messages should be addressed to
* @param selfDestruct true if this messenger must self close destruct when idle. <b>Warning:</b> If selfDestruct is used,
* this messenger will remained referenced for as long as isIdleImpl returns false.
*/
public BlockingMessenger(PeerGroupID homeGroupID, EndpointAddress dest, boolean selfDestruct) {
super(dest);
this.homeGroupID = homeGroupID;
stateMachine = new BlockingMessengerState(distributingListener);
/*
* Sets up a timer task that will close this messenger if it says to have become idle. It will keep it referenced
* until then.
* <p/>
* As long as this timer task is scheduled, this messenger is not subject to GC. Therefore, its owner, if any, which is strongly
* referenced, is not subject to GC either. This avoids prematurely closing open connections just because a destination is
* not currently in use, which we would have to do if CanonicalMessengers could be GCed independantly (and which would
* force us to use finalizers, too).<p/>
*
* Such a mechanism is usefull only if this blocking messenger is expensive to make or holds system resources that require
* an explicit invocation of the close method. Else, it is better to let it be GC'ed along with any refering canonical
* messenger when memory is tight.<p/>
*
*/
//
// FIXME 20040413 jice : we trust transports to implement isIdle reasonably, which may be a leap of faith. We
// should probably superimpose a time limit of our own.
//
if (selfDestruct) {
SelfCancellingTask selfDestructTask = new SelfCancellingTask() {
public void execute() {
try {
if (isIdleImpl()) {
close();
} else {
return;
}
} catch (Throwable uncaught) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Uncaught Throwable in selfDescructTask. ", uncaught);
}
}
}
};
ScheduledExecutorService scheduledExecutorService = TaskManager.getTaskManager().getScheduledExecutorService();
selfDestructTaskHandle = scheduledExecutorService.scheduleAtFixedRate(selfDestructTask, 60, 60, TimeUnit.SECONDS);
selfDestructTask.setHandle(selfDestructTaskHandle);
} else {
selfDestructTaskHandle = null;
}
}
/**
* Sets an owner for this blocking messenger. Owners are normally canonical messengers. The goal of registering the owner is
* to keep that owner reachable as long as this blocking messenger is. Canonical messengers are otherwise softly referenced,
* and so, may be deleted whenever memory is tight.
* <p/>
* We do not want to use finalizers or the equivalent reference queue mechanism; so we have no idea when a blocking messenger
* is no-longer referenced by any canonical. In addition it may be expensive to make and so we want to keep it for a while
* anyway. As a result, instead of keeping a blocking messenger open as long as there is a canonical, we do the opposite: we
* keep the canonical (owner, here) as long as the blocking messenger is open (and usually beyond, memory allowing). How long
* a blocking messenger will stay around depends upon that messenger's implementation. That may even be left up to the GC, in
* the end (if close is not needed AND the messenger is cheap to make). In that case, the owner is likely the only referrer,
* and so both will have the same lifetime.
*
* @param owner The object that should be kept referenced at least as long as this one.
*/
public void setOwner(Object owner) {
this.owner = owner;
}
/**
* Assemble a destination address for a message based upon the messenger
* default destination address and the optional provided overrides.
*
* @param service The destination service or {@code null} to use default.
* @param serviceParam The destination service parameter or {@code null} to
* use default.
*/
protected EndpointAddress getDestAddressToUse(String service, String serviceParam) {
EndpointAddress defaultAddress = getDestinationAddress();
EndpointAddress result;
if(null == service) {
if(null == serviceParam) {
// Use default service name and service params
result = defaultAddress;
} else {
// use default service name, override service params
result = new EndpointAddress(defaultAddress, defaultAddress.getServiceName(), serviceParam);
}
} else {
if(null == serviceParam) {
// override service name, use default service params (this one is pretty weird and probably not useful)
result = new EndpointAddress(defaultAddress, service, defaultAddress.getServiceParameter());
} else {
// override service name, override service params
result = new EndpointAddress(defaultAddress, service, serviceParam);
}
}
return result;
}
/**
* A transport may call this to cause an orderly closure of its messengers.
*/
protected final void shutdown() {
DeferredAction action;
synchronized (stateMachine) {
stateMachine.shutdownEvent();
action = eventCalled();
}
// We called an event. State may have changed.
notifyChange();
performDeferredAction(action);
}
/**
* {@inheritDoc}
* <p/>
* We overload isClosed because many messengers still use super.isClosed()
* as part of their own implementation or don't override it at all. They
* expect it to be true only when all is shutdown; not while we're closing
* gently.
*
* FIXME - jice@jxta.org 20040413: transports should get a deeper retrofit eventually.
*/
@Override
public boolean isClosed() {
return (!lieToOldTransports) && super.isClosed();
}
/**
* {@inheritDoc}
* <p/>
* getLogicalDestinationAddress() requires resolution (it's the address advertised by the other end).
* For a blocking messenger it's easy. We're born resolved. So, just ask the implementor what it is.
*/
public final EndpointAddress getLogicalDestinationAddress() {
return getLogicalDestinationImpl();
}
/**
* {@inheritDoc}
*
* <p/> Some transports historically overload the close method of BlockingMessenger.
* The final is there to make sure we know about it. However, there should be no
* harm done if the unchanged code is renamed to closeImpl; even if it calls super.close().
* The real problem, however, is transports calling close (was their own, but now it means this one), when
* they want to break. It will make things look like someone just called close, but it will not
* actually break anything. However, that will cause the state machine to go through the close process.
* this will end up calling closeImpl(). That will do.
*/
public final void close() {
DeferredAction action;
synchronized (stateMachine) {
stateMachine.closeEvent();
action = eventCalled();
}
// We called an event. State may have changed.
notifyChange();
performDeferredAction(action);
}
/**
* {@inheritDoc}
*/
public void sendMessageB(Message msg, String service, String serviceParam) throws IOException {
DeferredAction action;
synchronized (stateMachine) {
try {
while ((currentMessage != null) && !inputClosed) {
stateMachine.wait();
}
} catch (InterruptedException ie) {
throw new InterruptedIOException();
}
if (inputClosed) {
throw new IOException("Messenger is closed. It cannot be used to send messages");
}
// We store the four elements of a pending msg separately. We do not want to pour millions of tmp objects on the GC for
// nothing.
storeCurrent(msg, service, serviceParam);
stateMachine.saturatedEvent();
action = eventCalled();
}
notifyChange(); // We called an event. State may have changed.
performDeferredAction(action); // We called an event. There may be an action. (start, normally).
// After deferred action, the message was either sent or failed. (done by this thread).
// We can tell because, if failed, the currentMessage is still our msg.
Throwable failure = null;
synchronized (stateMachine) {
if (currentMessage == msg) {
failure = currentThrowable;
if (failure == null) {
failure = new IOException("Unknown error");
}
// Ok, let it go, now.
storeCurrent(null, null, null);
} // Else, don't touch currentMsg; it's not our msg.
}
if (failure == null) {
// No failure. Report ultimate succes.
msg.setMessageProperty(Messenger.class, OutgoingMessageEvent.SUCCESS);
return;
}
// Failure. See how we can manage to throw it.
if (failure instanceof IOException) {
throw (IOException) failure;
}
if (failure instanceof RuntimeException) {
throw (RuntimeException) failure;
}
if (failure instanceof Error) {
throw (Error) failure;
}
IOException failed = new IOException("Failure sending message");
failed.initCause(failure);
throw failed;
}
/**
* {@inheritDoc}
*/
public final boolean sendMessageN(Message msg, String service, String serviceParam) {
boolean queued = false;
DeferredAction action = DeferredAction.ACTION_NONE;
boolean closed;
synchronized (stateMachine) {
closed = inputClosed;
if ((!closed) && (currentMessage == null)) {
// We copy the four elements of a pending msg right here. We do not want to pour millions of tmp objects on the GC.
storeCurrent(msg, service, serviceParam);
stateMachine.saturatedEvent();
action = eventCalled();
queued = true;
}
}
if (queued) {
notifyChange(); // We called an event. State may have changed.
performDeferredAction(action); // We called an event. There may be an action. (start, normally).
// After deferred action, the message was either sent or failed. (done by this thread).
// We can tell because, if failed, the currentMessage is still our msg.
synchronized (stateMachine) {
if (currentMessage == msg) {
if (currentThrowable == null) {
currentThrowable = new IOException("Unknown error");
}
msg.setMessageProperty(Messenger.class, currentThrowable);
// Ok, let it go, now.
storeCurrent(null, null, null);
} else {
msg.setMessageProperty(Messenger.class, OutgoingMessageEvent.SUCCESS);
// Don't touch the current msg; it's not our msg.
}
}
// Yes, we return true in either case. sendMessageN is supposed to be async. If a message fails
// after it was successfuly queued, the error is not reported by the return value, but only by
// the message property (and select). Just making sure the behaviour is as normal as can be
// even it means suppressing some information.
return true;
}
// Not queued. Either closed, or currently sending. If inputClosed, that's what we report.
msg.setMessageProperty(Messenger.class,
closed ?
new OutgoingMessageEvent(msg, new IOException("This messenger is closed. " + "It cannot be used to send messages.")) :
OutgoingMessageEvent.OVERFLOW);
return false;
}
/**
* {@inheritDoc}
*/
public final void resolve() {// We're born resolved. Don't bother calling the event.
}
/**
* {@inheritDoc}
*/
public final int getState() {
return stateMachine.getState();
}
/**
* {@inheritDoc}
*/
public final Messenger getChannelMessenger(PeerGroupID redirection, String service, String serviceParam) {
// Our transport is always in the same group. If the channel's target group is the same, no group
// redirection is ever needed.
return new BlockingMessengerChannel(getDestinationAddress(),
homeGroupID.equals(redirection) ? null : redirection, service,
serviceParam);
}
/**
* Three exposed methods may need to inject new events in the system: sendMessageN, close, and shutdown. Since they can both
* cause actions, and since connectAction and startAction are deferred, it seems possible that one of the
* actions caused by send, close, or shutdown be called while connectAction or startAction are in progress.
*
* However, the state machine gives us a few guarantees: connectAction and startAction can never nest. We will not be
* asked to perform one while still performing the other. Only the synchronous actions closeInput, closeOutput, or
* failAll can possibly be requested in the interval. We could make more assumptions and simplify the code, but rather
* keep at least some flexibility.
*/
private void performDeferredAction(DeferredAction action) {
switch (action) {
case ACTION_SEND:
sendIt();
break;
case ACTION_CONNECT:
cantConnect();
break;
}
}
/**
* A shortHand for a frequently used sequence. MUST be called while synchronized on stateMachine.
*
* @return the deferred action.
*/
private DeferredAction eventCalled() {
DeferredAction action = deferredAction;
deferredAction = DeferredAction.ACTION_NONE;
stateMachine.notifyAll();
return action;
}
/**
* Performs the ACTION_SEND deferred action: sends the one msg in our one msg queue.
* This method *never* sets the outcome message property. This is left to sendMessageN and sendMessageB, because
* sendMessageB does not want to set it in any other case than success, while sendMessageN does it in all cases.
* The problem with that is: how do we communicate the outcome to sendMessage{NB} without having to keep
* the 1 msg queue locked until then (which would be in contradiction with how we interact with the state machine).
* To make it really inexpensive, here's the trick: when a message fails currentMessage and currentFailure remain.
* So the sendMessageRoutine can check them and known that it is its message and not another one that caused the
* failure. If all is well, currentMessage and currentFailure are nulled and if another message is send immediately
* sendMessage is able to see that its own message was processed fully. (this is a small cheat regarding the
* state of saturation after failall, but that's not actually detectable from the outside: input is closed
* before failall anyway. See failall for that part.
*/
private void sendIt() {
if (currentMessage == null) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Internal error. Asked to send with no message.");
}
return;
}
DeferredAction action;
try {
sendMessageBImpl(currentMessage, currentService, currentParam);
} catch (Throwable any) {
// Did not work. We report the link down and let the state machine tell us when to fail the msg. It is assumed that
// when this happens, the cnx is already down. FIXME - jice@jxta.org 20040413: check with the various kind of funky
// exception. Some may not mean the link is down
synchronized (stateMachine) {
currentThrowable = any;
stateMachine.downEvent();
action = eventCalled();
}
notifyChange();
performDeferredAction(action); // we expect connect but let the state machine decide.
return;
}
// Worked.
synchronized (stateMachine) {
storeCurrent(null, null, null);
stateMachine.idleEvent();
action = eventCalled();
}
// We did go from non-idle to idle. Report it.
notifyChange();
performDeferredAction(action); // should be none but let the state machine decide.
}
/**
* Performs the ACTION_CONNECT deferred action: generate a downEvent since we cannot reconnect.
*/
private void cantConnect() {
DeferredAction action;
synchronized (stateMachine) {
stateMachine.downEvent();
action = eventCalled();
}
notifyChange();
performDeferredAction(action); // should be none but let the state machine decide.
}
/*
* Abstract methods to be provided by implementer (a transport for example).
* To adapt legacy transport, keep extending BlockingMessenger and just rename your close, isIdle, sendMessage and
* getLogicalDestinationAddress methods to closeImpl, isIdleImpl, sendMessageBImpl, and getLogicalDestinationImpl, respectively.
*/
/**
* Close connection. May fail current send.
*/
protected abstract void closeImpl();
/**
* Send a message blocking as needed until the message is sent.
*
* @param message The message to send.
* @param service The destination service.
* @param param The destination serivce param.
* @throws IOException Thrown for errors encountered while sending the message.
*/
protected abstract void sendMessageBImpl(Message message, String service, String param) throws IOException;
/**
* return true if this messenger has not been used for a long time. The definition of long time is: "sufficient such that closing it
* is worth the cost of having to re-open". A messenger should self close if it thinks it meets the definition of
* idle. BlockingMessenger leaves the evaluation to the transport but does the work automatically. <b>Important:</b> if
* self destruction is used, this method must work; not just return false. See the constructor. In general, if closeImpl does
* not need to do anything, then self destruction is not needed.
*
* @return {@code true} if theis messenger is, by it's own definition, idle.
*/
protected abstract boolean isIdleImpl();
/**
* Obtain the logical destination address from the implementer (a transport for example).
*/
protected abstract EndpointAddress getLogicalDestinationImpl();
}