/*
* 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 java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.IntHolder;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.TIMEOUT;
import org.omg.CORBA.portable.IDLEntity;
import org.omg.CosEventChannelAdmin.AlreadyConnected;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNotification.EventHeader;
import org.omg.CosNotification.EventType;
import org.omg.CosNotification.FixedEventHeader;
import org.omg.CosNotification.Property;
import org.omg.CosNotification.StructuredEvent;
import org.omg.CosNotification.UnsupportedAdmin;
import org.omg.CosNotification.UnsupportedQoS;
import org.omg.CosNotifyChannelAdmin.AdminLimitExceeded;
import org.omg.CosNotifyChannelAdmin.ClientType;
import org.omg.CosNotifyChannelAdmin.InterFilterGroupOperator;
import org.omg.CosNotifyChannelAdmin.ObtainInfoMode;
import org.omg.CosNotifyChannelAdmin.StructuredProxyPushConsumer;
import org.omg.CosNotifyChannelAdmin.StructuredProxyPushConsumerHelper;
import org.omg.CosNotifyComm.InvalidEventType;
import org.omg.CosNotifyComm.StructuredPushSupplier;
import org.omg.CosNotifyComm.StructuredPushSupplierHelper;
import gov.sandia.NotifyMonitoringExt.EventChannel;
import gov.sandia.NotifyMonitoringExt.EventChannelFactory;
import gov.sandia.NotifyMonitoringExt.NameAlreadyUsed;
import gov.sandia.NotifyMonitoringExt.NameMapError;
import gov.sandia.NotifyMonitoringExt.SupplierAdmin;
import alma.ACSErrTypeCORBA.wrappers.AcsJCORBAReferenceNilEx;
import alma.ACSErrTypeCORBA.wrappers.AcsJNarrowFailedEx;
import alma.ACSErrTypeCommon.wrappers.AcsJBadParameterEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.ACSErrTypeCommon.wrappers.AcsJUnexpectedExceptionEx;
import alma.AcsNCTraceLog.LOG_NC_ConsumerProxyCreation_FAIL;
import alma.AcsNCTraceLog.LOG_NC_ConsumerProxyCreation_OK;
import alma.AcsNCTraceLog.LOG_NC_TaoExtensionsSubtypeMissing;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.container.ContainerServicesBase;
import alma.acs.exceptions.AcsJException;
import alma.acs.logging.AcsLogLevel;
import alma.acs.nc.AcsEventPublisher;
import alma.acsnc.EventDescription;
import alma.acsnc.EventDescriptionHelper;
import alma.acsnc.OSPushSupplierPOA;
import alma.acsncErrType.wrappers.AcsJPublishEventFailureEx;
/**
* NCPublisher is the Notificaction Channel implementation to be used with the
* event channel API to publish events using the Java programming language.
* It has been created out of the old <code>SimpleSupplier</code> class,
* to integrate its functionality into the ContainerServices and and to allow
* using transport mechanisms other than Corba NC under the hood.
* <p>
* Design note on CORBA usage (generally not relevant to ACS NC users):
* The IDL-struct-data is wrapped by a corba Any, but then pushed on the notification channel inside a "Structured Event"
* (with the Any object in StructuredEvent#filterable_data[0]).
* Don't confuse this with Corba's option of sending events directly as Anys.
* As of 2006-12, HSO is not sure why this complex design was chosen, instead of using structured events without the Any wrapping inside.
* Possibly it offers some flexibility for generic consumer tools written in languages that have no introspection.
* <p>
* Note about refactoring: NCPublisher gets instantiated in module jcont using java reflection.
* Thus if you change the package, name, or constructor of this class,
* make sure to fix {@link alma.acs.container.ContainerServicesImpl#CLASSNAME_NC_PUBLISHER}
* or its use to get the constructor.
* <p>
* @TODO (HSO): figure out if the CORBA impl is thread safe. Fix this class accordingly,
* or document that it is not thread safe otherwise.
*
* @param <T> The event (base) type. If all events published by an instance of this class are of the same type,
* e.g., <code>MyEventStructFromIDL</code>, then that type should be used;
* otherwise the common base type <code>IDLEntity</code> should be used. <br>
* Note that we currently cannot use the restriction <code>T extends IDLEntity</code>
* because of <code>ArchiveSupplier</code> which <code>extends NCPublisher<Object></code>.
*
* @author jslopez, hsommer
*/
public class NCPublisher<T> extends OSPushSupplierPOA implements AcsEventPublisher<T>, ReconnectableParticipant {
/** Provides code shared among suppliers and consumers. */
protected Helper helper;
/** The event channel has exactly one name registered in the naming service. */
protected final String channelName;
/** The channel notification service domain name, can be <code>null</code>. */
protected final String channelNotifyServiceDomainName;
/**
* Supplier Admin object is responsible for creating & managing proxy consumers.
* Unlike in NCSubscriber, we do not (yet) share admin objects on the supplier side,
* so that each NCPublisher instance is responsible alone for its server-side admin object.
*/
protected SupplierAdmin supplierAdmin;
/**
* The server-side proxy consumer object used by this supplier to push events onto the channel.
*/
protected StructuredProxyPushConsumer proxyConsumer;
/**
* The total number of successful events published by this particular
* supplier. The current count is attached to the EventDescription that gets
* sent along as additional data (remainder_of_body).
*/
protected final AtomicLong count = new AtomicLong(0);
/** Channel we'll be sending events to. Cannot be final because reconnect() modifies it. */
protected EventChannel channel;
/** Provides access to the ACS logging system. */
protected final Logger logger;
/** To access the ORB among other things */
protected final ContainerServicesBase services;
/** Helper class used to manipulate CORBA anys */
protected final AnyAide anyAide;
protected AcsNcReconnectionCallback reconnectCallback;
/**
* Optional event processing handler. May be <code>null</code>.
* @see #registerEventProcessingCallback(alma.acs.nc.AcsEventPublisher.EventProcessingHandler)
*/
protected EventProcessingHandler<T> eventProcessingHandler;
/**
* If the user registers {@link #eventProcessingHandler}, then we also create this queue;
* otherwise it remains null.
* <p>
* The queue stores events during Notify Service failures, to allow re-sending them later.
* The re-sending is implemented in a rather simple way, without using a separate timer and thread;
* future attempts to publish other events simply check if the queue contains older events,
* which then get sent first.
*/
protected CircularQueue<T> eventQueue;
/**
* Monitor for access to {@link #eventQueue} and {@link #eventProcessingHandler}.
* <p>
* We assume that compared to the Corba transport of events
* the overhead to synchronize threads using this object is negligible,
* so that we don't bother with fancier synchronization techniques.
*/
protected final Object eventQueueSync = new Object();
/** Whether sending of events should be logged */
private boolean isTraceEventsEnabled;
/**
* Whether autoreconnecting to the channel should be done when publishing events throw exception OBJECT_NOT_EXIST
*/
private boolean autoreconnect;
/**
* Creates a new instance of NCPublisher. Make sure you call
* {@link #disconnect()} when you no longer need this event supplier object.
*
* @param channelName
* Name of the notification channel events will be published to.
* @param services
* This is used to get the name of the component and to access
* the ACS logging system.
* @throws AcsJException
* There are literally dozens of CORBA exceptions that could be
* thrown by the NCPublisher class. Instead, these are converted
* into an ACS Error System exception for the developer's
* convenience.
*/
public NCPublisher(String channelName, ContainerServicesBase services, NamingContext namingService)
throws AcsJException {
this(channelName, null, services, namingService);
}
/**
* Creates a new instance of NCPublisher. Make sure you call
* {@link #disconnect()} when you no longer need this event supplier object.
*
* @param channelName
* name of the notification channel events will be published to.
* @param channelNotifyServiceDomainName
* Channel domain name, which is being used to determine
* notification service.
* @param services
* This is used to get the name of the component and to access
* the ACS logging system.
* @throws AcsJException
* There are literally dozens of CORBA exceptions that could be
* thrown by the NCPublisher class. Instead, these are
* converted into an ACS Error System exception for the
* developer's convenience.
*/
public NCPublisher(String channelName, String channelNotifyServiceDomainName,
ContainerServicesBase services, NamingContext namingService) throws AcsJException {
if (channelName == null) {
Throwable cause = new Throwable("Null reference obtained for the channel name!");
throw new AcsJBadParameterEx(cause);
}
if (services == null) {
AcsJBadParameterEx ex = new AcsJBadParameterEx();
ex.setParameter("services");
ex.setParameterValue("null");
throw ex;
}
if (namingService == null) {
AcsJBadParameterEx ex = new AcsJBadParameterEx();
ex.setParameter("namingService");
ex.setParameterValue("null");
throw ex;
}
this.channelName = channelName;
this.channelNotifyServiceDomainName = channelNotifyServiceDomainName;
this.services = services;
this.autoreconnect = false;
logger = services.getLogger();
anyAide = new AnyAide(this.services);
helper = null;
init(namingService);
}
/**
* Initializes NCPublisher
* @param namingService Naming service
* @throws AcsJException
* There are literally dozens of CORBA exceptions that could be
* thrown by the NCPublisher class. Instead, these are
* converted into an ACS Error System exception for the
* developer's convenience.
*/
protected synchronized void init(NamingContext namingService) throws AcsJException {
helper = new Helper(channelName, channelNotifyServiceDomainName, this.services, namingService);
isTraceEventsEnabled = helper.getChannelProperties().isTraceEventsEnabled(this.channelName);
// get the channel
// @TODO: handle Corba TIMEOUT
channel = helper.getNotificationChannel(getNotificationFactoryName());
// Corba NC spec about adminId: a unique identifier assigned by the target EventChannel instance that is unique among all
// SupplierAdmin instances currently associated with the channel.
// We are currently not using it, but it could be logged to help with NC debugging.
IntHolder adminIdHolder = new IntHolder();
org.omg.CosNotifyChannelAdmin.SupplierAdmin supplierAdminBase = null;
try {
supplierAdminBase = channel.new_for_suppliers(InterFilterGroupOperator.AND_OP, adminIdHolder);
} catch (TIMEOUT ex) { // found in http://jira.alma.cl/browse/COMP-6312
throw new AcsJCORBAProblemEx(ex);
}
if (supplierAdminBase == null) {
AcsJCORBAReferenceNilEx ex = new AcsJCORBAReferenceNilEx();
ex.setVariable("supplierAdminBase");
ex.setContext("Null reference obtained for the supplier admin for channel " + this.channelName);
throw ex;
}
try {
supplierAdmin = gov.sandia.NotifyMonitoringExt.SupplierAdminHelper.narrow(supplierAdminBase);
} catch (BAD_PARAM ex) {
// This should never happen, since we already enforced the presence of TAO extensions in Helper#initializeNotifyFactory
String specialSupplierAdminId = gov.sandia.NotifyMonitoringExt.SupplierAdminHelper.id();
String standardSupplierAdminId = org.omg.CosNotifyChannelAdmin.SupplierAdminHelper.id();
LOG_NC_TaoExtensionsSubtypeMissing.log(logger, channelName + "-SupplierAdmin", specialSupplierAdminId, standardSupplierAdminId);
AcsJNarrowFailedEx ex2 = new AcsJNarrowFailedEx(ex);
ex2.setNarrowType(specialSupplierAdminId);
throw ex2;
}
int proxyCreationAttempts = 0;
while( proxyConsumer == null) {
String randomizedClientName = Helper.createRandomizedClientName(services.getName());
// Holder for the unique ID assigned by the admin object. It is different from the name we set, and will be discarded.
IntHolder proxyIdHolder = new IntHolder();
proxyCreationAttempts++;
try {
// Create the consumer proxy (to which the published events will be fed) with a name.
// The client type parameter selects a StructuredProxyPushConsumer (based on Structured Events),
// as opposed to ProxyPushConsumer (based on Anys), or SequenceProxyPushConsumer (based on sequences of Structured Events).
org.omg.CORBA.Object tempCorbaObj = supplierAdmin.obtain_named_notification_push_consumer(ClientType.STRUCTURED_EVENT, proxyIdHolder, randomizedClientName.toString());
if (tempCorbaObj == null) {
AcsJCORBAReferenceNilEx ex = new AcsJCORBAReferenceNilEx();
ex.setVariable("tempCorbaObj");
ex.setContext("Null reference obtained for the Proxy Push Consumer for publisher " + services.getName());
// @TODO destroy supplierAdmin
throw ex;
}
proxyConsumer = StructuredProxyPushConsumerHelper.narrow(tempCorbaObj);
LOG_NC_ConsumerProxyCreation_OK.log(logger, proxyIdHolder.value, randomizedClientName, proxyCreationAttempts, services.getName(), channelName, getNotificationFactoryName());
} catch (NameAlreadyUsed e) {
// Hopefully we won't run into this situation. Still, try to go on in the loop,
// with a different client name next time.
logger.fine("Consumer proxy name '" + randomizedClientName + "' already in use. Will try again with different random number appended.");
} catch (NameMapError ex) {
// Default to the unnamed version
try {
proxyConsumer = StructuredProxyPushConsumerHelper.narrow(supplierAdmin.obtain_notification_push_consumer(ClientType.STRUCTURED_EVENT, proxyIdHolder));
LOG_NC_ConsumerProxyCreation_OK.log(logger, proxyIdHolder.value, "-unknown-", proxyCreationAttempts, services.getName(), channelName, getNotificationFactoryName());
} catch (AdminLimitExceeded ex2) {
LOG_NC_ConsumerProxyCreation_FAIL.log(logger, services.getName(), channelName, getNotificationFactoryName(), ex2.getMessage());
// @TODO destroy supplierAdmin
throw new AcsJCORBAProblemEx(ex2);
}
} catch (AdminLimitExceeded e) {
LOG_NC_ConsumerProxyCreation_FAIL.log(logger, services.getName(), channelName, getNotificationFactoryName(), e.getMessage());
// @TODO destroy supplierAdmin
throw new AcsJCORBAProblemEx(e);
}
}
// Avoid future calls from the NC to #subscription_change(EventType[], EventType[]). See Corba spec 3.4.1.3
// @TODO: If we use ALL_NOW_UPDATES_ON then we could actually suppress sending of event types that no consumer wants to get.
proxyConsumer.obtain_subscription_types(ObtainInfoMode.NONE_NOW_UPDATES_OFF);
// must connect this StructuredPushSupplier to the proxy consumer, or
// events would never be sent anywhere.
// see 3.4.4.1 of Notification Service, v1.1
try {
StructuredPushSupplier thisSps = StructuredPushSupplierHelper
.narrow(this.services.activateOffShoot(this));
proxyConsumer.connect_structured_push_supplier(thisSps);
} catch (AcsJContainerServicesEx e) {
// convert it to an ACS Error System Exception
// @TODO destroy supplierAdmin and proxyConsumer
throw new AcsJCORBAProblemEx(e);
} catch (AlreadyConnected e) {
// Think there is virtually no chance of this every happening but...
// @TODO destroy supplierAdmin and proxyConsumer
throw new AcsJCORBAProblemEx(e);
}
reconnectCallback = new AcsNcReconnectionCallback(this, logger);
reconnectCallback.registerForReconnect(services, helper.getNotifyFactory());
}
/**
* User code <b>must call this method when the Supplier is no longer useful</b>.
* Failure to do so can result in remote memory leaks. User should not call
* this method multiple times either. Once disconnect has been called, all
* of NCPublisher's methods will cease to function properly.
* @throws AcsJIllegalStateEventEx if called when already disconnected.
*/
@Override
public synchronized void disconnect() throws AcsJIllegalStateEventEx {
if (supplierAdmin == null) {
throw new AcsJIllegalStateEventEx("Publisher already disconnected");
}
String errMsg = "Failed to cleanly disconnect NCPublisher for channel '" + channelName + "': ";
// Disconnect this supplier from the server-side proxy
if (proxyConsumer != null) {
try {
proxyConsumer.disconnect_structured_push_consumer();
} catch (Throwable thr) {
logger.log(Level.WARNING, errMsg + "could not disconnect push consumer", thr);
}
}
try {
supplierAdmin.destroy();
} catch (Throwable thr) {
logger.log(Level.WARNING, errMsg + "could not destroy supplier admin", thr);
}
try {
// clean-up CORBA stuff
services.deactivateOffShoot(this);
} catch (Throwable thr) {
logger.log(Level.WARNING, errMsg + "could not deactivate the NCPublisher offshoot.", thr);
}
reconnectCallback = null;
proxyConsumer = null;
supplierAdmin = null;
}
/**
* This method returns the notify service name as registered with the
* CORBA Naming Service. This is normally equivalent to
* acscommon::ALMADOMAIN. The sole reason this method is provided is to
* accomodate subclasses which subscribe/publish non-ICD style events (ACS
* archiving channel for example).In that case, the developer would override
* this method.
*
* @return string
*/
protected String getNotificationFactoryName() {
return helper.getNotificationFactoryNameForChannel();
}
/**
* This method sets the attribute autoreconnect which is used to determine if
* a reconnection to the channel must be done when the exception OBJECT_NOT_EXIST
* is thrown during the publication of an event.
* @param autoreconnect Whether autoreconneting to the channel should be done
*/
public void setAutoreconnect(boolean autoreconnect) {
this.autoreconnect = autoreconnect;
}
/**
* This method gets called by the CORBA framework to notify us that the subscriber
* situation has changed. See 2.6.3 of Notification Service, v1.1
* <p>
* ACS does not provide an implementation of this method.
* Probably because of not fully understanding the possibilities of the
* obtain_subscription_types call (see c'tor above) in the past, we assumed that
* a supplier could not know whether there is more than one subscriber listening
* for a given event type on our NC, so that the subscription_change information coming from
* a single subscriber would not be good enough to make us drop certain event types
* on the supplier side.
* <p>
* @TODO: Check this optimization potential in the future.
*
* @see org.omg.CosNotifyComm.NotifySubscribeOperations#subscription_change(org.omg.CosNotification.EventType[],
* org.omg.CosNotification.EventType[])
*/
public void subscription_change(EventType[] added, EventType[] removed) throws InvalidEventType {
throw new NO_IMPLEMENT(); // suggested by corba spec, in case the supplier does not want to be notified.
}
/**
* The CORBA NC spec (3.3.10.1) says:
* "The disconnect_structured_push_supplier operation is invoked to
* terminate a connection between the target StructuredPushSupplier
* and its associated consumer. This operation takes no input
* parameters and returns no values. The result of this operation is
* that the target StructuredPushSupplier will release all resources
* it had allocated to support the connection, and dispose its own
* object reference."
* In the ACS NC design the life cycle of an NCPublisher is unaffected
* by that of consumers. Thus we only log a FINE message here.
*/
public void disconnect_structured_push_supplier() {
String msg = "A Consumer has disconnected from the '" + channelName + "' channel";
logger.fine(msg);
}
/**
* Method which queue an event that couldn't be published
*
* @param se A complete structured event
* @param customData The user-supplied event data, needed for eventProcessingHandler notification.
*/
protected void queueUnpublishedEvent(StructuredEvent se, T customData) {
synchronized (eventQueueSync) {
if (eventQueue != null) {
CircularQueue<T>.Data dropped = eventQueue.push(se, customData);
eventProcessingHandler.eventStoredInQueue(customData);
if (dropped != null) {
eventProcessingHandler.eventDropped(dropped.userData);
}
}
}
}
/**
* Method which publishes an entire CORBA StructuredEvent without making any
* modifications to it.
*
* @param se A complete structured event
* @param customData The user-supplied event data, needed for eventProcessingHandler notification.
* @throws AcsJException
* if the event cannot be published for some reason or another.
*/
protected void publishCORBAEvent(StructuredEvent se, T customData) throws AcsJException {
try {
// Publish directly the given event (see CORBA NC spec 3.3.7.1)
proxyConsumer.push_structured_event(se);
// Log successful sending of event (if event tracing is enabled)
if (isTraceEventsEnabled) {
// TODO: use type-safe log
logger.log(Level.INFO, "Channel:" + channelName + ", Event Type:" + customData.getClass().getSimpleName());
}
// Notify user (if handler is registered)
synchronized (eventQueueSync) {
if (eventQueue != null) {
eventProcessingHandler.eventSent(customData);
}
}
} catch (org.omg.CORBA.TRANSIENT ex) {
// the Notify Service is down...
// @TODO: Shouldn't we do the same also for some of the other SystemExceptions caught below?
queueUnpublishedEvent(se, customData);
// Throw the exception caught
//throw ex;
// Throw an exception
String reason = "Failed to publish event on channel '" + channelName + "': org.omg.CORBA.TRANSIENT was thrown.";
AcsJCORBAProblemEx jex = new AcsJCORBAProblemEx();
jex.setInfo(reason);
throw jex;
} catch (org.omg.CORBA.OBJECT_NOT_EXIST ex) {
if(true == autoreconnect) {
boolean reconnected = false;
// Disconnect
try {
disconnect();
} catch(Throwable thr) {}
// Reconnect to the channel
try {
init(helper.getNamingService());
reconnected = true;
} catch(Throwable thr) {}
if(reconnected) {
// Try to publish the event again
try {
// Publish directly the given event (see CORBA NC spec 3.3.7.1)
proxyConsumer.push_structured_event(se);
// Log successful sending of event (if event tracing is enabled)
if (isTraceEventsEnabled) {
// TODO: use type-safe log
logger.log(Level.INFO, "Channel:" + channelName + ", Event Type:" + customData.getClass().getSimpleName());
}
// Notify user (if handler is registered)
synchronized (eventQueueSync) {
if (eventQueue != null) {
eventProcessingHandler.eventSent(customData);
}
}
} catch(Throwable thr) {
queueUnpublishedEvent(se, customData);
}
} else {
queueUnpublishedEvent(se, customData);
}
} else {
// Throw an exception
String reason = "Failed to publish event on channel '" + channelName + "': org.omg.CORBA.OBJECT_NOT_EXIST was thrown.";
AcsJCORBAProblemEx jex = new AcsJCORBAProblemEx();
jex.setInfo(reason);
throw jex;
}
} catch (org.omg.CosEventComm.Disconnected e) {
// declared CORBA ex
String reason = "Failed to publish event on channel '" + channelName + "': org.omg.CosEventComm.Disconnected was thrown.";
AcsJCORBAProblemEx jex = new AcsJCORBAProblemEx();
jex.setInfo(reason);
throw jex;
} catch (org.omg.CORBA.SystemException ex) {
// CORBA runtime ex (with minor code)
String reason = "Failed to publish event on channel '"
+ channelName + "': " + ex.getClass().getName()
+ " was thrown.";
AcsJCORBAProblemEx jex = new AcsJCORBAProblemEx(ex);
jex.setMinor(ex.minor);
jex.setInfo(reason);
throw jex;
} catch (Throwable thr) {
// other ex
Throwable cause = new Throwable(
"Failed to publish event on channel '" + channelName + "'. " + thr.getMessage());
AcsJUnexpectedExceptionEx jex = new AcsJUnexpectedExceptionEx(cause);
throw jex;
}
}
/**
* Method used to create a pre-filled CORBA event.
*
* @param typeName
* The structured event's type_name.
* @param eventName
* Name of the event.
* @return A pre-filled CORBA event.
*/
protected StructuredEvent getCORBAEvent(String typeName, String eventName) {
// return value
StructuredEvent event = new StructuredEvent();
// event.header.fixed_header.event_type
String channelDomain = alma.acscommon.ALMADOMAIN.value;
EventType event_type = new EventType(channelDomain, typeName);
FixedEventHeader fixed_header = new FixedEventHeader(event_type, eventName);
// event.header.variable_header
Property[] variable_header = new Property[0];
// event.header
event.header = new EventHeader(fixed_header, variable_header);
return event;
}
/**
* Takes the given Java object and tries to pack it into a CORBA Any and
* publish it to the notification channel.
* This will fail if the parameter is not
* CORBA-generated from a user-defined IDL struct. In simple terms, trying
* to publish native Java types is impossible because they have no CORBA
* mapping to say Python or C++ types.
*
* @param customStruct
* An instance of the IDL struct (Java class) to be published.
* @throws AcsJPublishEventFailureEx If <code>customStruct</code> is not an IDL struct,
* for which it must be a subclass of IDLEntity.
* @throws AcsJException
* There are an enormous amount of possibilities pertaining to
* why an AcsJException would be thrown by publishEvent.
*/
@Override
public void publishEvent(T customStruct) throws AcsJException {
// Let's first verify that the use of generics between base class and here is OK also for the DDS side.
if( !(customStruct instanceof IDLEntity) ) {
String msg = "ACS is using Corba NC as the underlying pub/sub framework. Event data must be IDL-defined structs that inherit from org.omg.CORBA.portable.IDLEntity.";
AcsJPublishEventFailureEx ex = new AcsJPublishEventFailureEx();
ex.setFailureDescription(msg);
ex.setChannelName(channelName);
ex.setEventName(customStruct.getClass().getName());
throw ex;
}
IDLEntity customStructEntity = (IDLEntity)customStruct;
String typeName = customStructEntity.getClass().getSimpleName();
// Event to send
// Header: domain_name = "ALMA", type_name = simple name of IDL struct, event_name = ""
StructuredEvent event = getCORBAEvent(typeName, "");
// Send event meta data (client name, timestamp, counter) in the accompanying EventDescription object,
// which we transmit as the Any field 'remainder_of_body'.
event.remainder_of_body = services.getAdvancedContainerServices().getAny();
EventDescription descrip = new EventDescription(services.getName(),
alma.acs.util.UTCUtility.utcJavaToOmg(System.currentTimeMillis()), count.getAndIncrement());
EventDescriptionHelper.insert(event.remainder_of_body, descrip);
// In the 'filterable_data' field, we send our IDL struct coded as an Any
event.filterable_data = new Property[1];
event.filterable_data[0] = new Property(
alma.acscommon.DEFAULTDATANAME.value, anyAide.complexObjectToCorbaAny(customStructEntity));
// Check the queue for events from previous failures.
synchronized (eventQueueSync) {
if (eventQueue != null) {
CircularQueue<T>.Data tmp;
try {
while ((tmp = eventQueue.pop()) != null) {
publishCORBAEvent(tmp.corbaData, tmp.userData);
}
} catch (Exception ex) {
Level lev = ( isTraceEventsEnabled ? Level.INFO : Level.FINEST );
logger.log(lev, "Failed to flush event queue.", ex);
// go on and try to send the new event, to at least have it inserted into the queue.
}
}
}
try {
publishCORBAEvent(event, customStruct);
} catch(AcsJCORBAProblemEx ex) {
String info = ex.getInfo();
if(false == info.contains("org.omg.CORBA.TRANSIENT")) {
throw ex;
}
}
}
@Override
public void reconnect(EventChannelFactory ecf) {
logger.log(AcsLogLevel.NOTICE, "Reconnecting publisher with channel '" + channelName + "' after Notify Service recovery");
if (channel != null) {
channel = helper.getNotificationChannel(ecf);
if (channel == null) {
logger.log(Level.WARNING, "Cannot reconnect to the channel: " + channelName + "'");
return;
}
}
try {
channel.set_qos(helper.getChannelProperties().getCDBQoSProps(channelName));
channel.set_admin(helper.getChannelProperties().getCDBAdminProps(channelName));
} catch (UnsupportedQoS e) {
} catch (AcsJException e) {
} catch (UnsupportedAdmin ex) {
logger.warning(helper.createUnsupportedAdminLogMessage(ex));
}
}
@Override
public void enableEventQueue(int queueSize, EventProcessingHandler<T> handler) {
synchronized (eventQueueSync) {
// allow user also to update the handler
eventProcessingHandler = handler;
// queue can be created only once of course
if (eventQueue == null) {
eventQueue = new CircularQueue<T>(queueSize);
}
}
}
}