/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2009
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package alma.acs.nc;
import static alma.acs.nc.sm.generated.EventSubscriberAction.createConnection;
import static alma.acs.nc.sm.generated.EventSubscriberAction.createEnvironment;
import static alma.acs.nc.sm.generated.EventSubscriberAction.destroyConnection;
import static alma.acs.nc.sm.generated.EventSubscriberAction.destroyEnvironment;
import static alma.acs.nc.sm.generated.EventSubscriberAction.resumeConnection;
import static alma.acs.nc.sm.generated.EventSubscriberAction.suspendConnection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
import org.apache.commons.scxml.ErrorReporter;
import org.apache.commons.scxml.EventDispatcher;
import org.apache.commons.scxml.SCInstance;
import org.apache.commons.scxml.TriggerEvent;
import alma.ACSErrTypeCommon.wrappers.AcsJBadParameterEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.ACSErrTypeCommon.wrappers.AcsJStateMachineActionEx;
import alma.acs.container.ContainerServicesBase;
import alma.acs.exceptions.AcsJException;
import alma.acs.logging.MultipleRepeatGuard;
import alma.acs.logging.RepeatGuard;
import alma.acs.logging.RepeatGuard.Logic;
import alma.acs.nc.sm.generated.EventSubscriberAction;
import alma.acs.nc.sm.generated.EventSubscriberSignal;
import alma.acs.nc.sm.generated.EventSubscriberSignalDispatcher;
import alma.acs.nc.sm.generic.AcsScxmlActionDispatcher;
import alma.acs.nc.sm.generic.AcsScxmlActionExecutor;
import alma.acs.nc.sm.generic.AcsScxmlEngine;
import alma.acs.util.StopWatch;
import alma.acsErrTypeLifeCycle.wrappers.AcsJEventSubscriptionEx;
import alma.acsnc.EventDescription;
/**
* Base class for an event subscriber, that can be used for both Corba NC and for in-memory test NC.
* <p>
* We will have to see if it can also be used for DDS-based events, or if the required modifications will be too heavy.
*
* @param <T> The event (base) type. If all events are of the same type then that type should be used;
* otherwise a common base type for all events that may be sent on the given "channel" should be used,
* such as <code>Object</code> or <code>IDLEntity</code>.
*/
public abstract class AcsEventSubscriberImplBase<T>
implements AcsEventSubscriber<T>, AcsScxmlActionExecutor<EventSubscriberAction> {
/**
* A name that identifies the client of this NCSubscriber, to be used
* both for logging and also (if applicable) as the supplier proxy name so that
* looking at the proxy objects of the NC we can figure out who the clients are.
*/
protected final String clientName;
protected final ContainerServicesBase services;
/**
* Provides access to the ACS logging system.
*/
protected final Logger logger;
/**
* State machine for the subscriber lifecycle.
* It is not used for data handling itself, even though the measured overhead
* of less than 0.2 ms per signal might allow this.
*/
protected final AcsScxmlEngine<EventSubscriberSignal, EventSubscriberAction> stateMachine;
private static final String scxmlFileName = "/alma/acs/nc/sm/generated/EventSubscriberSCXML.xml";
protected final EventSubscriberSignalDispatcher stateMachineSignalDispatcher;
/**
* Event queue should hold at least two events to avoid unnecessary scary logs about slow receivers,
* but must be short enough to get receivers to actually implement their own queue and discard mechanism
* instead of relying on this ACS queue which may buffer events for a limited time and thus obscure the problem.
* @see #eventHandlingExecutor
*/
public static final int EVENT_QUEUE_CAPACITY = 50;
/**
* Single-thread executor with a small queue (size given in {@link #EVENT_QUEUE_CAPACITY},
* used to stay responsive toward the NC even if receivers are slow.
* The purpose is only to track down receivers that don't keep up with the event rate, and not to provide reliable event buffering,
* see http://jira.alma.cl/browse/COMP-5767.
* <p>
* Errors are logged, using also fields {@link #numEventsDiscarded} and {@link #receiverTooSlowLogRepeatGuard}.
* <p>
* The difference of this mechanism compared to the logs controlled by {@link #processTimeLogRepeatGuard} is that
* here we take into account the actual event rate and check whether the receiver can handle it,
* whereas {@link #processTimeLogRepeatGuard} only compares the actual process times against pre-configured values.
*/
private ThreadPoolExecutor eventHandlingExecutor;
/**
* @see #eventHandlingExecutor
*/
private final AtomicLong numEventsDiscarded = new AtomicLong(0);
/**
* Throttles logs from {@link #logEventProcessingTooSlowForEventRate(long, String, long)}.
*/
private final RepeatGuard receiverTooSlowLogRepeatGuard;
/**
* Contains a generic receiver to be used by the
* {@link #addGenericSubscription()} method.
*/
protected AcsEventSubscriber.GenericCallback genericReceiver;
/**
* Contains a list of receiver functions to be invoked when an event
* of a particular type is received.
* <p>
* key = the event type (Java class derived from IDL struct). <br>
* value = the matching event handler.
*/
protected final Map<Class<? extends T>, Callback<? extends T>> receivers =
new HashMap<Class<? extends T>, Callback<? extends T>>();
/**
* Contains a list of repeat guards for each different type of event.
*/
protected final MultipleRepeatGuard processTimeLogRepeatGuard;
/**
* Runtime access to the type parameter <T>.
*/
protected final Class<T> eventType;
/**
* @return <code>true</code> if events should be logged when they are received.
*/
protected abstract boolean isTraceEventsEnabled();
/**
* Base class constructor, to be called from subclass ctor.
* IMPORTANT: Subclasses MUST call "stateMachine.setUpEnvironment()" at the end of their constructors.
* This is because Java does not support template method design for constructors
* (see for example http://stackoverflow.com/questions/2906958/running-a-method-after-the-constructor-of-any-derived-class).
* Since the subclasses are all meant to be produced within ACS I did not want to go for dirty tricks to work around this.
* We also don't want to call a public init() method after constructing the subscriber.
* <p>
* Normally an ACS class such as container services will act as the factory for event subscriber objects,
* but for exceptional cases it is also possible to create one stand-alone,
* as long as the required parameters can be provided.
*
* @param services
* To get ACS logger, access to the CDB, etc.
* @param clientName
* A name that identifies the client of this NCSubscriber.
* TODO: Check if we still need this name to be specified separately from {@link services#getName()}.
* @throws AcsJException
* Thrown on any <I>really bad</I> error conditions encountered.
*/
public AcsEventSubscriberImplBase(ContainerServicesBase services, String clientName, Class<T> eventType) throws AcsJException {
if (services == null) {
AcsJBadParameterEx ex = new AcsJBadParameterEx();
ex.setParameter("services");
ex.setParameterValue("null");
throw ex;
}
this.services = services;
if (clientName == null) {
AcsJBadParameterEx ex = new AcsJBadParameterEx();
ex.setParameter("clientName");
ex.setParameterValue("null");
throw ex;
}
this.clientName = clientName;
this.eventType = eventType;
logger = services.getLogger();
// @TODO Set more realistic guarding parameters, e.g. max 1 identical log in 10 seconds
processTimeLogRepeatGuard = new MultipleRepeatGuard(0, TimeUnit.SECONDS, 1, Logic.COUNTER, 100);
// log slow receiver error only every 30 seconds or every 100 times, whatever comes first
receiverTooSlowLogRepeatGuard = new RepeatGuard(30, TimeUnit.SECONDS, 100, Logic.OR);
// set up the state machine
AcsScxmlActionDispatcher<EventSubscriberAction> actionDispatcher =
new AcsScxmlActionDispatcher<EventSubscriberAction>(logger, EventSubscriberAction.class);
// As a design choice, we implement all SM actions directly here in this class and its subclasses.
// The state machine framework expects an object per action, so that we must register ourselves for every action type.
// We could do this as a for loop over EventSubscriberAction.values(), but better hardcode the registration
// for every action type so that SM changes will be noticed more easily.
actionDispatcher.registerActionHandler(createEnvironment, this);
actionDispatcher.registerActionHandler(destroyEnvironment, this);
actionDispatcher.registerActionHandler(createConnection, this);
actionDispatcher.registerActionHandler(destroyConnection, this);
actionDispatcher.registerActionHandler(suspendConnection, this);
actionDispatcher.registerActionHandler(resumeConnection, this);
// Here the AcsScxmlEngine constructor will load the scxml file and start the state machine
stateMachine = new AcsScxmlEngine<EventSubscriberSignal, EventSubscriberAction>(
scxmlFileName, logger, actionDispatcher, EventSubscriberSignal.class);
// Convenience method to send events to the SM.
// Usually meant to be used as a base class, but here we don't want to expose the SM to the user.
stateMachineSignalDispatcher = new EventSubscriberSignalDispatcher() {
@Override
protected AcsScxmlEngine<EventSubscriberSignal, EventSubscriberAction> getScxmlEngine() {
return stateMachine;
}
};
// Later at the end of the subclass ctor there should be the call to
// stateMachine.setUpEnvironment(), leaving our state machine in state EnvironmentCreated
}
////////////////////////////////////////////////////////////////////////////////////////
/////////////////////// State machine //////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
/**
* Dispatches the action enum to one of the action handler methods.
* <p>
* The overhead of implementing this method is the price we pay for avoiding reflection
* and allowing flexible action implementation in the SM design.
* @see alma.acs.nc.sm.generic.AcsScxmlActionExecutor#execute(java.lang.Enum, org.apache.commons.scxml.EventDispatcher, org.apache.commons.scxml.ErrorReporter, org.apache.commons.scxml.SCInstance, java.util.Collection)
*/
@Override
public boolean execute(EventSubscriberAction action, EventDispatcher evtDispatcher, ErrorReporter errRep,
SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
// logger.fine("Will handle action " + action.name());
boolean ret = true;
switch (action) {
case createEnvironment:
createEnvironmentAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
case destroyEnvironment:
destroyEnvironmentAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
case createConnection:
createConnectionAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
case destroyConnection:
destroyConnectionAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
case suspendConnection:
suspendAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
case resumeConnection:
resumeAction(evtDispatcher, errRep, scInstance, derivedEvents);
break;
default:
ret = false;
}
// logger.fine("Done handling action " + action.name());
return ret;
}
/**
* Subclass may override, but must call super.createEnvironmentAction().
*/
protected void createEnvironmentAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
// nada
}
/**
* Subclass may override, but must call super.destroyEnvironmentAction().
*/
protected void destroyEnvironmentAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
}
/**
* Subclass may override, but must call super.createConnectionAction().
*/
protected void createConnectionAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
eventHandlingExecutor = new ThreadPoolExecutor(0, 1, 1L, TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(EVENT_QUEUE_CAPACITY), services.getThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );
}
/**
* Handler for "destroyConnection" state machine action.
* <p>
* Shuts down the event queue.
* Queued events may still be processed by the receivers afterwards,
* but here we wait for up to 500 ms to log it if it is the case.
* <p>
* Further events delivered to {@link #processEventAsync(Object, EventDescription)}
* will cause an exception there.
* <p>
* Subclass may override, but must call super.destroyConnectionAction().
*/
protected void destroyConnectionAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
eventHandlingExecutor.shutdown();
boolean queueOK = false; // just in case we want to report the success of this shutdown
try {
queueOK = eventHandlingExecutor.awaitTermination(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
// just leave queueOK == false
}
if (!queueOK) {
// interrupted or timeout occurred, may still have events in the queue. Terminate with error message
int remainingEvents = eventHandlingExecutor.getQueue().size();
logQueueShutdownError(500, remainingEvents);
}
}
/**
* Subclass may override, but must call super.suspendAction().
*/
protected void suspendAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
// nada
}
/**
* Subclass may override, but must call super.resumeAction().
*/
protected void resumeAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
// nada
}
/**
* Gets the configured (or default) max time that a receiver may take to process an event,
* regardless of the actual event rate.
*/
protected abstract double getMaxProcessTimeSeconds(String eventName);
/**
* Logs an exception thrown by an event handler (user code).
*/
protected abstract void logEventReceiveHandlerException(String eventName, String receiverClassName, Throwable thr);
/**
* Logs the error that event processing time was exceeded.
*/
protected abstract void logEventProcessingTimeExceeded(String eventName, long logOcurrencesNumber);
/**
* Logs the error that the receiver cannot keep up with the actual event rate,
* in spite of the small event buffering done.
*
* @param numEventsDiscarded The number of events that have actually been discarded since the last log.
* Will often be 0 when we just warn about the queue filling up.
* @param eventName
*/
protected abstract void logEventProcessingTooSlowForEventRate(long numEventsDiscarded, String eventName);
/**
* Logs or ignores the fact that an event was received for which no receiver could be found.
* <p>
* The subclass must know whether such a condition is expected or not,
* e.g. because event filtering is set up outside of the subscriber
* and only subscribed event types are expected to arrive.
*/
protected abstract void logNoEventReceiver(String eventName);
/**
* Logs the error that the local event buffer could not be emptied before shutting down the subscriber.
*/
protected abstract void logQueueShutdownError(int timeoutMillis, int remainingEvents);
/**
* Asynchronously calls {@link #processEvent(Object, EventDescription)},
* using {@link #eventHandlingExecutor}.
* <p>
* This method should be called from the subclass-specific method that receives the event,
* for example <code>push_structured_event</code> in case of Corba NC.
* <p>
* This method is thread-safe.
*
* @param eventData (defined as <code>Object</code> instead of <code>T</code> to include data for generic subscription).
* @param eventDesc event meta data
*/
protected void processEventAsync(final Object eventData, final EventDescription eventDesc) {
// to avoid unnecessary scary logs, we tolerate previous events up to half the queue size
boolean isReceiverBusyWithPreviousEvent = ( eventHandlingExecutor.getQueue().size() > EVENT_QUEUE_CAPACITY / 2 );
// logger.info("Queue size: " + eventHandlingExecutor.getQueue().size());
boolean thisEventDiscarded = false;
try {
eventHandlingExecutor.execute(
new Runnable() {
public void run() {
// here we call processEvent from the worker thread
processEvent(eventData, eventDesc);
}
});
} catch (RejectedExecutionException ex) {
// receivers have been too slow, queue is actually full, will drop data.
thisEventDiscarded = true;
numEventsDiscarded.incrementAndGet();
}
if (thisEventDiscarded || isReceiverBusyWithPreviousEvent) {
// receiverTooSlowLogRepeatGuard currently not thread-safe, therefore synchronize
synchronized (receiverTooSlowLogRepeatGuard) {
if (receiverTooSlowLogRepeatGuard.checkAndIncrement()) {
// About numEventsDiscarded and concurrency:
// That counter may have been incremented by other threads between the above RejectedExecutionException
// and here. These threads are blocked now, and have not yet incremented the repeat guard.
// This can lead to some harmless irregularities in how often we actually log the message.
// What matters is that we report correctly the number of discarded events.
logEventProcessingTooSlowForEventRate(
numEventsDiscarded.getAndSet(0),
eventData.getClass().getName());
}
}
}
}
/**
* This method should be called from the subclass-specific method that receives the event,
* for example <code>push_structured_event</code> in case of Corba NC,
* or preferably via {@link #processEventAsync(Object, EventDescription)}.
* <p>
* No exception is allowed to be thrown by this method, even if the receiver implementation throws a RuntimeExecption
* @param eventData (defined as <code>Object</code> instead of <code>T</code> to include data for generic subscription).
* @param eventDesc
*/
protected void processEvent(Object eventData, EventDescription eventDesc) {
Class<?> incomingEventType = eventData.getClass();
String eventName = incomingEventType.getName();
// figure out how much time this event has to be processed (according to configuration)
double maxProcessTimeSeconds = getMaxProcessTimeSeconds(eventName);
StopWatch profiler = new StopWatch();
// we give preference to a receiver that has registered for this event type T or a subtype
if (eventType.isAssignableFrom(incomingEventType) && receivers.containsKey(incomingEventType)) {
@SuppressWarnings("unchecked")
T typedEventData = (T) eventData;
Callback<? extends T> receiver = receivers.get(incomingEventType);
profiler.reset();
try {
_process(receiver, typedEventData, eventDesc);
}
catch (Throwable thr) {
logEventReceiveHandlerException(eventName, receiver.getClass().getName(), thr);
}
double usedSecondsToProcess = (profiler.getLapTimeMillis() / 1000.0);
// warn the end-user if the receiver is taking too long, using a repeat guard
if (usedSecondsToProcess > maxProcessTimeSeconds && processTimeLogRepeatGuard.checkAndIncrement(eventName)) {
logEventProcessingTimeExceeded(eventName, processTimeLogRepeatGuard.counterAtLastExecution(eventName));
}
}
// fallback to generic receive method
else if (genericReceiver != null) {
profiler.reset();
genericReceiver.receiveGeneric(eventData, eventDesc);
double usedSecondsToProcess = (profiler.getLapTimeMillis() / 1000.0);
// warn the end-user if the receiver is taking too long
if (usedSecondsToProcess > maxProcessTimeSeconds && processTimeLogRepeatGuard.checkAndIncrement(eventName)) {
logEventProcessingTimeExceeded(eventName, processTimeLogRepeatGuard.counterAtLastExecution(eventName));
}
}
// No receiver found.
// This may be OK or not, depending on whether the subclass sets up filtering in the underlying notification framework
// that ensures that only subscribed event types reach the subscriber. Subclass should decide if and how to report this.
else {
logNoEventReceiver(eventName);
}
}
/**
* "Generic helper method" to enforce type argument inference by the compiler,
* see http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ207
*/
private <U extends T> void _process(Callback<U> receiver, T eventData, EventDescription eventDescrip) {
U castCorbaData = null;
try {
castCorbaData = receiver.getEventType().cast(eventData);
}
catch (ClassCastException ex) {
// This should never happen and would be an ACS error
logger.warning("Failed to deliver incompatible data '" + eventData.getClass().getName() +
"' to subscriber '" + receiver.getEventType().getName() + "'. Fix data subscription handling in " + getClass().getName() + "!");
}
// user code errors (runtime ex etc) we let fly up
receiver.receive(castCorbaData, eventDescrip);
}
@Override
public String getLifecycleState() {
return stateMachine.getCurrentState();
}
/**
* Use only for unit testing!
*/
public boolean hasGenericReceiver() {
return ( genericReceiver != null );
}
/**
* Use only for unit testing!
*/
public int getNumberOfReceivers() {
return receivers.size();
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////// AcsEventSubscriber impl //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
/**
* Subscribes to all events. The latest generic receiver displaces the previous one.
* <p>
* If in addition to this generic subscription we also have specific subscriptions via
* {@link #addSubscription(Class, Callback)},
* then those more specific subscriptions will take precedence in receiving an event.
* <p>
* Notice though that any server-side filters previously created for the event type specific
* subscriptions get deleted when calling this method, so that even after removing a generic
* subscription the network performance gain of server-side filtering is lost.
* @TODO: Couldn't this be fixed by creating the server-side filters on demand (see also javadoc class comment about lifecycle)?
*/
@Override
public final void addGenericSubscription(GenericCallback receiver) throws AcsJEventSubscriptionEx {
// First time we create the filter and set the receiver
if( genericReceiver == null ) {
notifyFirstSubscription(null);
}
// After the filter is created, we just replace the receiver
genericReceiver = receiver;
}
/**
* Removes the generic receiver handler.
*
* @throws AcsJCORBAProblemEx
*/
@Override
public final void removeGenericSubscription() throws AcsJEventSubscriptionEx {
if (genericReceiver == null ) {
AcsJEventSubscriptionEx ex = new AcsJEventSubscriptionEx();
ex.setContext("Failed to remove generic subscription when not actually subscribed.");
ex.setEventType("generic");
throw ex;
}
notifySubscriptionRemoved(null);
genericReceiver = null;
}
@Override
public final <U extends T> void addSubscription(Callback<U> receiver)
throws AcsJEventSubscriptionEx {
Class<U> subscribedEventType = receiver.getEventType();
if (subscribedEventType == null || !(eventType.isAssignableFrom(subscribedEventType)) ) {
AcsJEventSubscriptionEx ex = new AcsJEventSubscriptionEx();
ex.setContext("Receiver is returning a null or invalid event type. " +
"Check the getEventType() method implementation and try again.");
ex.setEventType(subscribedEventType == null ? "null" : subscribedEventType.getName());
throw ex;
}
// First time we create the filter and set the receiver
if (!receivers.containsKey(subscribedEventType)) {
notifyFirstSubscription(subscribedEventType);
}
// After the filter is created, we just replace the corresponding receivers
receivers.put(subscribedEventType, receiver);
}
@Override
public final <U extends T> void removeSubscription(Class<U> structClass) throws AcsJEventSubscriptionEx {
// Removing subscription from receivers list
if (structClass != null) {
if (receivers.containsKey(structClass)) {
receivers.remove(structClass);
notifySubscriptionRemoved(structClass);
}
else {
AcsJEventSubscriptionEx ex = new AcsJEventSubscriptionEx();
ex.setContext("Trying to unsubscribe from an event type not being subscribed to.");
ex.setEventType(structClass.getName());
throw ex;
}
}
else {
// Removing every type of event
receivers.clear();
notifyNoSubscription();
}
}
@Override
public final void startReceivingEvents() throws AcsJIllegalStateEventEx, AcsJCouldntPerformActionEx {
try {
stateMachineSignalDispatcher.startReceivingEvents();
} catch (AcsJStateMachineActionEx ex) {
throw new AcsJCouldntPerformActionEx(ex);
}
}
/**
* @see alma.acs.nc.AcsEventSubscriber#disconnect()
*/
@Override
public final void disconnect() throws AcsJIllegalStateEventEx, AcsJCouldntPerformActionEx {
try {
stateMachineSignalDispatcher.stopReceivingEvents();
}
catch (AcsJIllegalStateEventEx ex) {
// ignore. If the state is totally wrong then the subsequent cleanUpEnvironment
// will also throw AcsJIllegalStateEventEx
}
catch (AcsJStateMachineActionEx ex) {
throw new AcsJCouldntPerformActionEx(ex);
}
finally {
// even after AcsJIllegalStateEventEx in stopReceivingEvents we want to do the second clean-up step
try {
stateMachineSignalDispatcher.cleanUpEnvironment();
} catch (AcsJStateMachineActionEx ex) {
throw new AcsJCouldntPerformActionEx(ex);
}
}
}
@Override
public final void suspend() throws AcsJIllegalStateEventEx, AcsJCouldntPerformActionEx {
try {
stateMachineSignalDispatcher.suspend();
} catch (AcsJStateMachineActionEx ex) {
throw new AcsJCouldntPerformActionEx(ex);
}
}
@Override
public final void resume() throws AcsJIllegalStateEventEx, AcsJCouldntPerformActionEx {
try {
stateMachineSignalDispatcher.resume();
} catch (AcsJStateMachineActionEx ex) {
throw new AcsJCouldntPerformActionEx(ex);
}
}
////////////////////////////////////////////////////////////////////////////////////////
///////////////////////// Subscription helper methods //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
/**
* @param structClass Can be <code>null</code> in case of generic subscription.
*/
protected abstract void notifyFirstSubscription(Class<?> structClass) throws AcsJEventSubscriptionEx;
/**
* @param structClass
* @throws AcsJEventSubscriptionEx
*/
protected abstract void notifySubscriptionRemoved(Class<?> structClass) throws AcsJEventSubscriptionEx;
/**
*
*/
protected abstract void notifyNoSubscription();
@Override
public boolean isSuspended() {
return stateMachine.isStateActive("EnvironmentCreated::Connected::Suspended");
}
public final boolean isDisconnected() {
return (
stateMachine.isStateActive("EnvironmentCreated::Disconnected") ||
stateMachine.isStateActive("EnvironmentUnknown")
);
}
}