/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* 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.testsupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
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.AcsJIllegalStateEventEx;
import alma.ACSErrTypeCommon.wrappers.AcsJStateMachineActionEx;
import alma.acs.container.ContainerServicesBase;
import alma.acs.exceptions.AcsJException;
import alma.acs.nc.AcsEventSubscriberImplBase;
import alma.acsErrTypeLifeCycle.wrappers.AcsJEventSubscriptionEx;
import alma.acsnc.EventDescription;
/**
* In-memory subscriber.
* @param <T> See {@link AcsEventSubscriberImplBase}.
* @see InMemoryNcFake
*/
class InMemorySubscriber<T> extends AcsEventSubscriberImplBase<T>
{
/**
* Reference to factory.
*/
private final InMemoryNcFake nc;
/**
* Used for {@link InMemorySubscriber#suspendBuffer}.
*/
private static class CachedEvent {
CachedEvent(Object eventData, EventDescription eventDesc) {
this.eventData = eventData;
this.eventDesc = eventDesc;
}
Object eventData;
EventDescription eventDesc;
}
/**
* Buffer for data we got while being suspended.
* Currently of unlimited size.
*/
private final List<CachedEvent> suspendBuffer;
/**
* @param nc
* @param services
* @param clientName
* @param eventType
* @throws AcsJException
*/
InMemorySubscriber(InMemoryNcFake nc, ContainerServicesBase services, String clientName, Class<T> eventType)
throws AcsJException {
super(services, clientName, eventType);
this.nc = nc;
suspendBuffer = new ArrayList<CachedEvent>();
// this call is required, see base class ctor
stateMachineSignalDispatcher.setUpEnvironment();
}
/**
* InMemoryNcFake should call this method.
* See <code>NCSubscriber#push_structured_event(StructuredEvent</code>.
* @throws AcsJIllegalStateEventEx If this subscriber is disconnected.
*/
void pushData(Object eventData, EventDescription eventDesc) throws AcsJIllegalStateEventEx {
// Here we use the state machine in the data flow, something we do not yet
// dare to do in the real NCSubscriber, being afraid of performance risks. Seems fine though.
if (isDisconnected()) {
AcsJIllegalStateEventEx ex = new AcsJIllegalStateEventEx("Subscriber '" + clientName + "' is disconnected.");
ex.setState("disconnected");
// todo ex.set context... instead of above message
throw ex;
}
if (eventData == null) {
// see LOG_NC_EventReceive_FAIL
logger.warning("Received 'null' event.");
}
else {
// Here we fake server-side suspension by storing the event in a local queue.
// TODO: Would be nice to reuse eventHandlingExecutor from the base class,
// but currently we cannot tap into the queue-receive chain and the data is attached to Runnable objects.
// If the state machine call is too slow then we could also work with "suspendBuffer != null" logic.
if (isSuspended()) {
synchronized (suspendBuffer) {
suspendBuffer.add(new CachedEvent(eventData, eventDesc));
}
}
else {
// Here we fake server-side filtering by skipping the event processing if we
// know that the event type cannot be handled
if (hasGenericReceiver() || receivers.containsKey(eventData.getClass())) {
// TODO: Log something as in LOG_NC_EventReceive_OK
processEventAsync(eventData, eventDesc);
}
}
}
}
/**
* Gives access to the client name.
*/
String getClientName() {
return clientName;
}
////////////////////////////////////////////////////////////////////////////////////////
/////////////////////// State machine actions //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
protected void createEnvironmentAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance, Collection<TriggerEvent> derivedEvents)
throws AcsJStateMachineActionEx {
super.createEnvironmentAction(evtDispatcher, errRep, scInstance, derivedEvents);
}
protected void destroyEnvironmentAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
super.destroyEnvironmentAction(evtDispatcher, errRep, scInstance, derivedEvents);
}
protected void createConnectionAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
super.createConnectionAction(evtDispatcher, errRep, scInstance, derivedEvents);
}
protected void destroyConnectionAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
nc.disconnectSubscriber(this);
super.destroyConnectionAction(evtDispatcher, errRep, scInstance, derivedEvents);
}
protected void suspendAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
super.suspendAction(evtDispatcher, errRep, scInstance, derivedEvents);
// nothing else to do. We'll just collect incoming events in suspendBuffer
}
protected void resumeAction(EventDispatcher evtDispatcher, ErrorReporter errRep, SCInstance scInstance,
Collection<TriggerEvent> derivedEvents) throws AcsJStateMachineActionEx {
super.resumeAction(evtDispatcher, errRep, scInstance, derivedEvents);
// async re-sending of suspendBuffer data
final ArrayList<CachedEvent> oldBuffer = new ArrayList<CachedEvent>(suspendBuffer.size());
synchronized (suspendBuffer) {
oldBuffer.addAll(suspendBuffer);
suspendBuffer.clear();
}
Runnable processor = new Runnable() {
@Override
public void run() {
try {
for (CachedEvent cachedEvent : oldBuffer) {
// if in the meantime we get suspended again, suspendBuffer will again hold our data.
pushData(cachedEvent.eventData, cachedEvent.eventDesc);
}
} catch (AcsJIllegalStateEventEx ex) {
logger.log(Level.WARNING, "Failed to deliver buffered events (suspended time) because subscriber is now disconnected.", ex);
}
}
};
services.getThreadFactory().newThread(processor).start();
}
////////////////////////////////////////////////////////////////////////////////////////
/////////////////////// Various template method impls //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
@Override
protected boolean isTraceEventsEnabled() {
return false;
}
@Override
protected double getMaxProcessTimeSeconds(String eventName) {
// make this configurable if needed
return 2.0;
}
@Override
protected void logEventReceiveHandlerException(String eventName, String receiverClassName, Throwable thr) {
logger.log(Level.WARNING, "The registered event handler of type '" +
receiverClassName + "' illegally threw an exception for event '" + eventName + "'.", thr);
}
@Override
protected void logEventProcessingTimeExceeded(String eventName, long logOcurrencesNumber) {
logger.warning("Took too long to process event '" + eventName + "' (logOcurrencesNumber=" + logOcurrencesNumber + ").");
}
@Override
protected void logEventProcessingTooSlowForEventRate(long numEventsDiscarded, String eventName) {
logger.warning("More events came in from the NC than the receiver processed. eventName=" + eventName +
"; numEventsDiscarded=" + numEventsDiscarded + ".");
}
@Override
protected void logNoEventReceiver(String eventName) {
// we fake server-side filtering #pushData and thus have to treat missing matching receiver as a problem
logger.warning("logNoEventReceiver: clientName=" + clientName + ", eventName=" + eventName);
}
@Override
protected void logQueueShutdownError(int timeoutMillis, int remainingEvents) {
// TODO Auto-generated method stub
}
@Override
protected void notifyFirstSubscription(Class<?> structClass) {
// nothing
}
@Override
protected void notifySubscriptionRemoved(Class<?> structClass) throws AcsJEventSubscriptionEx {
// nothing
}
@Override
protected void notifyNoSubscription() {
// nothing
}
}