/*******************************************************************************
* 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 acs.benchmark.nc.comp.subscriber;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.omg.CORBA.portable.IDLEntity;
import acs.benchmark.nc.comp.CorbaNotifyBaseImpl;
import alma.ACSErrTypeCommon.CouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.logging.AcsLogLevel;
import alma.acs.nc.AcsEventSubscriber;
import alma.acs.nc.AcsEventSubscriber.Callback;
import alma.acs.util.StopWatch;
import alma.acsnc.EventDescription;
import alma.benchmark.CorbaNotifyConsumerOperations;
import alma.benchmark.LightweightMountStatusData;
import alma.benchmark.MountStatusData;
import alma.benchmark.NcEventSpec;
import alma.benchmark.SomeOtherEventType;
public class CorbaNotifyConsumerImpl extends CorbaNotifyBaseImpl<AcsEventSubscriber<IDLEntity>> implements CorbaNotifyConsumerOperations
{
private AtomicBoolean receiveMethodCalled = new AtomicBoolean(false);
/**
* This field will hold the timestamp of the first event received,
* no matter which one of the receivers got it.
*/
private final AtomicLong firstEventReceivedTimeNanos = new AtomicLong(-1);
private final AtomicInteger eventsReceivedCounter = new AtomicInteger(0);
/**
* Will be null in case of 'infinite' event subscription
*/
private CountDownLatch sharedEventCountdown;
// @Override
// public void initialize(ContainerServices containerServices) throws ComponentLifecycleException {
// super.initialize(containerServices);
// }
//
// @Override
// public void cleanUp() throws AcsJComponentCleanUpEx {
// super.cleanUp();
// }
@Override
protected AcsEventSubscriber<IDLEntity> createNcParticipant(String ncName) throws AcsJContainerServicesEx {
AcsEventSubscriber<IDLEntity> ret = m_containerServices.createNotificationChannelSubscriber(ncName, IDLEntity.class);
m_logger.info("Created subscriber for NC=" + ncName + "; state=" + ret.getLifecycleState());
return ret;
}
@Override
protected void disconnectNcParticipant(AcsEventSubscriber<IDLEntity> sub) throws AcsJIllegalStateEventEx, AcsJCouldntPerformActionEx {
sub.disconnect();
}
/**
* The event handler used for all NC subscriptions, one instance per NC and per event type.
* @param <T> The event type
*/
protected class TestEventHandler<T extends IDLEntity> implements Callback<T> {
private final Class<T> clazz;
private final String antennaName;
private final int processingDelayMillis;
/**
* @param clazz Class of the event (IDL struct)
* @param antennaName Expected antenna name in MountStatusData or LightweightMountStatusData events.
* May be null for other events or if antenna name does not matter. Currently not used anyway.
*/
protected TestEventHandler(Class<T> clazz, String antennaName, int processingDelayMillis) {
this.clazz = clazz;
this.antennaName = antennaName;
this.processingDelayMillis = processingDelayMillis;
}
@Override
public Class<T> getEventType() {
return clazz;
}
@Override
public void receive(T event, EventDescription eventDescrip) {
// initial event stuff
firstEventReceivedTimeNanos.compareAndSet(-1, System.nanoTime());
// event counters
int eventCount = eventsReceivedCounter.incrementAndGet();
if (sharedEventCountdown != null) {
sharedEventCountdown.countDown();
}
// progress logging
int modDiv = logMultiplesOfEventCount.get();
if (modDiv > 0 && eventCount % modDiv == 0) {
m_logger.log(AcsLogLevel.DEBUG, "Have received a total of " + eventCount + " events, last was a " + event.getClass().getSimpleName());
}
// simulate event processing, if specified
if (processingDelayMillis > 0) {
try {
Thread.sleep(processingDelayMillis);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
/**
* Creates a typed event handler for the given eventName.
* To avoid errors, the String-to-handler mapping is not done in a generic way (classForName etc with guessing the Java package)
* but hardcoded for a few known event struct names that can be used for the tests.
*
* @param eventName Currently supported are "MountStatusData", "LightweightMountStatusData", "SomeOtherEventType"
* @return
* @throws IllegalArgumentException If eventName is not supported.
*/
protected TestEventHandler<? extends IDLEntity> createEventHandler(
String eventName,
String antennaName,
int processingDelayMillis) {
TestEventHandler<? extends IDLEntity> ret = null;
if (eventName.equals("MountStatusData")) {
ret= new TestEventHandler<MountStatusData>(MountStatusData.class, antennaName, processingDelayMillis) {
public void receive(MountStatusData event, EventDescription eventDescrip) {
super.receive(event, eventDescrip);
// TODO: do something with event.antennaName;
}
};
}
else if (eventName.equals("LightweightMountStatusData")) {
ret= new TestEventHandler<LightweightMountStatusData>(LightweightMountStatusData.class, antennaName, processingDelayMillis);
}
else if (eventName.equals("SomeOtherEventType")) {
ret= new TestEventHandler<SomeOtherEventType>(SomeOtherEventType.class, antennaName, processingDelayMillis);
}
else {
throw new IllegalArgumentException("Unsupported event type '" + eventName + "'.");
}
return ret;
}
@Override
public int receiveEvents(NcEventSpec[] ncEventSpecs, int processingDelayMillis, int numberOfEvents)
throws CouldntPerformActionEx {
if (receiveMethodCalled.getAndSet(true)) {
AcsJCouldntPerformActionEx ex = new AcsJCouldntPerformActionEx("Method receiveEvents can be called only once during the component lifecycle.");
throw ex.toCouldntPerformActionEx();
}
if (cancel) {
AcsJCouldntPerformActionEx ex = new AcsJCouldntPerformActionEx("Method receiveEvents cannot be called after interrupt / ncDisconnect.");
throw ex.toCouldntPerformActionEx();
}
m_logger.info("Will receive events on " + ncEventSpecs.length + " NC(s), with processingDelayMillis=" + processingDelayMillis
+ ", numberOfEvents=" + (numberOfEvents > 0 ? numberOfEvents : "infinite") );
// Set up receivers
// sync object used to wait for numberOfEvents (if specified)
if (numberOfEvents > 0) {
sharedEventCountdown = new CountDownLatch(numberOfEvents);
}
StopWatch sw = new StopWatch();
try {
// iterate over NCs
for (NcEventSpec ncEventSpec : ncEventSpecs) {
AcsEventSubscriber<IDLEntity> sub = subsOrPubs.get(ncEventSpec.ncName);
if (sub == null) {
throw new AcsJCouldntPerformActionEx("No subscriber available for NC '" + ncEventSpec.ncName + "'.");
}
// else {
// m_logger.info("Dealing with subscriber for NC=" + ncEventSpec.ncName + ", which is in state "+ sub.getLifecycleState());
// }
// iterate over event types
for (String eventName : ncEventSpec.eventNames) {
sub.addSubscription(createEventHandler(eventName, ncEventSpec.antennaName, processingDelayMillis));
m_logger.info("Added subscription for event=" + eventName + ", NC=" + ncEventSpec.ncName);
}
sub.startReceivingEvents();
}
} catch (AcsJCouldntPerformActionEx ex) {
ex.printStackTrace();
throw ex.toCouldntPerformActionEx();
} catch (Exception ex) {
ex.printStackTrace();
throw new AcsJCouldntPerformActionEx(ex).toCouldntPerformActionEx();
}
m_logger.info(ncEventSpecs.length + " subscriber(s) set up to receive events, which took " + sw.getLapTimeMillis() + " ms.");
if (numberOfEvents > 0) {
m_logger.info("Will wait for a total of " + numberOfEvents + " events to be received, with timeout after 10 minutes.");
try {
boolean cleanTermination = sharedEventCountdown.await(10, TimeUnit.MINUTES);
if (cleanTermination) {
m_logger.info("Received the expected " + numberOfEvents + " events in " + sw.getLapTimeMillis() + " ms.");
}
else {
m_logger.warning("Unforeseen termination of event suppliers after 10 min (timeout).");
cancel = true;
}
} catch (InterruptedException ex) {
cancel = true;
}
}
else {
m_logger.info("Will return from receiveEvents now but will keep receiving events.");
}
if (cancel) {
throw new AcsJCouldntPerformActionEx("Event receiving was interrupted or failed otherwise.").toCouldntPerformActionEx();
}
else {
long receptionTimeNanos = ( numberOfEvents > 0 && firstEventReceivedTimeNanos.longValue() > 0
? (System.nanoTime() - firstEventReceivedTimeNanos.longValue())
: -1 );
return (int) TimeUnit.NANOSECONDS.toMillis(receptionTimeNanos);
}
}
}