/* * ALMA - Atacama Large Millimiter Array (c) Associated Universities Inc., 2002 * (c) European Southern Observatory, 2002 Copyright by ESO (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 * * Consumer.java * * Created on March 5, 2003, 4:02 PM */ // ----------------------------------------------------------------------//// package alma.acs.nc; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; 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.portable.IDLEntity; import org.omg.CosNotification.EventType; 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.ConsumerAdmin; import org.omg.CosNotifyChannelAdmin.InterFilterGroupOperator; import org.omg.CosNotifyChannelAdmin.StructuredProxyPushSupplier; import org.omg.CosNotifyChannelAdmin.StructuredProxyPushSupplierHelper; import org.omg.CosNotifyFilter.ConstraintExp; import org.omg.CosNotifyFilter.Filter; import org.omg.CosNotifyFilter.FilterFactory; import gov.sandia.NotifyMonitoringExt.EventChannel; import gov.sandia.NotifyMonitoringExt.EventChannelFactory; import gov.sandia.NotifyMonitoringExt.NameAlreadyUsed; import gov.sandia.NotifyMonitoringExt.NameMapError; import alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx; import alma.ACSErrTypeCommon.wrappers.AcsJIllegalArgumentEx; import alma.ACSErrTypeJavaNative.wrappers.AcsJJavaAnyEx; import alma.AcsNCTraceLog.LOG_NC_EventReceive_HandlerException; import alma.AcsNCTraceLog.LOG_NC_ReceiverTooSlow; import alma.AcsNCTraceLog.LOG_NC_SubscriptionConnect_OK; import alma.JavaContainerError.wrappers.AcsJContainerServicesEx; import alma.acs.container.ContainerServicesBase; import alma.acs.container.ContainerServicesImpl; import alma.acs.exceptions.AcsJException; import alma.acs.logging.AcsLogLevel; import alma.acs.logging.RepeatGuard; import alma.acs.logging.RepeatGuard.Logic; import alma.acs.util.StopWatch; import alma.acsnc.EventDescription; import alma.acsnc.EventDescriptionHelper; import alma.acsnc.OSPushConsumer; import alma.acsnc.OSPushConsumerHelper; import alma.acsnc.OSPushConsumerPOA; import alma.acsncErrType.wrappers.AcsJEventSubscriptionFailureEx; /** * Consumer is the Java implementation of a structured push consumer * notification channel class. In short, this class is used to receive events * asynchronously from notification channel suppliers. It can either be used * as-is (assuming receivers/handlerFunctions are being utilized) or subclassed. * Developers must remember to use the disconnect method after they are done * receiving events. * * @author dfugate * @deprecated Since ACS 10.2. Use {@link ContainerServicesBase#createNotificationChannelSubscriber(String, Class)} instead. */ public class Consumer extends OSPushConsumerPOA implements ReconnectableParticipant{ protected static final String RECEIVE_METHOD_NAME = "receive"; /** * The default maximum amount of time an event handler is given to process * event before an exception is logged. This is used when an end user does * *not* define the appropriate XML elements within the ACS CDB. * See the EventChannel.xsd for more info, e.g. at * http://www.eso.org/projects/alma/develop/acs/OnlineDocs/ACS_docs/schemas/urn_schemas-cosylab-com_EventChannel_1.0/complexType/EventDescriptor.html#attr_MaxProcessTime . */ private static final double DEFAULT_MAX_PROCESS_TIME = 2000.0; /** helper object contains various info about the notification channel */ protected final ChannelInfo m_channelInfo; /** used to time the execution of receive methods */ private final StopWatch profiler; /** Provides access to the ACS logging system. */ protected final Logger m_logger; /** * Used only as logging parameter. */ private final String m_clientName; /** Provides access to the naming service among other things. */ protected final Helper m_helper; /** * Name of the notification service that hosts the channel that we consume event from. */ protected final String m_notifyServiceName; /** The channel has exactly one name registered in the CORBA Naming Service. */ protected final String m_channelName; /** * There can be only one notification channel for any given consumer. */ protected EventChannel m_channel; /** The channel notification service domain name, can be <code>null</code>. */ protected final String m_channelNotifyServiceDomainName; /** * 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 type-safe {@link LOG_NC_ReceiverTooSlow}, using also fields {@link #numEventsDiscarded} and {@link #receiverTooSlowLogRepeatGuard}. */ private final ThreadPoolExecutor eventHandlingExecutor; /** * @see #eventHandlingExecutor */ private long numEventsDiscarded; /** * Throttles {@link LOG_NC_ReceiverTooSlow} logs, see {@link #eventHandlingExecutor}. */ private final RepeatGuard receiverTooSlowLogRepeatGuard; /** * Contains a list of handler/receiver objects whose "receive" method (see {@link #RECEIVE_METHOD_NAME}) * will be invoked when an event of a particular type is received. */ protected final HashMap<String, Object> m_handlerFunctions = new HashMap<String, Object>(); /** * maps event names to the maximum amount of time allowed for * receiver methods to complete. Time is given in floating point seconds. */ protected final HashMap<String, Double> m_handlerTimeoutMap; /** * The consumer admin object used by consumers to get a reference to the * structured supplier proxy. */ protected ConsumerAdmin m_consumerAdmin; /** The supplier proxy we are connected to. */ protected StructuredProxyPushSupplier m_proxySupplier; /** CORBA reference to ourself */ protected OSPushConsumer m_corbaRef; /** Helper class used to manipulate CORBA anys */ protected AnyAide m_anyAide; /** Whether sending of events should be logged */ private final boolean isTraceEventsEnabled; private IntHolder proxyID; private AcsNcReconnectionCallback m_callback; private final ReentrantLock disconnectLock = new ReentrantLock(); /** * Creates a new instance of Consumer * * @param channelName * Subscribe to events on this channel registered in the CORBA * Naming Service. * @param services * This is used to access ACS logging system. * @throws AcsJException * Thrown on any <I>really bad</I> error conditions encountered. */ public Consumer(String channelName, ContainerServicesBase services) throws AcsJException { this(channelName, null, services); } /** * Creates a new instance of Consumer * * @param channelName * Subscribe to events on this channel registered in the CORBA * Naming Service. * @param channelNotifyServiceDomainName * Channel domain name, which is being used to determine notification service. May be <code>null</code>. * @param services * This is used to access ACS logging system. * @throws AcsJException * Thrown on any <I>really bad</I> error conditions encountered. */ public Consumer(String channelName, String channelNotifyServiceDomainName, ContainerServicesBase services) throws AcsJException { // sanity checks if (channelName == null) { AcsJIllegalArgumentEx ex = new AcsJIllegalArgumentEx(); ex.setVariable("channelName"); ex.setValue("null"); throw ex; } if (services == null) { AcsJIllegalArgumentEx ex = new AcsJIllegalArgumentEx(); ex.setVariable("services"); ex.setValue("null"); throw ex; } m_channelName = channelName; m_channelNotifyServiceDomainName = channelNotifyServiceDomainName; m_logger = services.getLogger(); m_clientName = services.getName(); // naming service, POA, and Any generator m_helper = new Helper(channelName, channelNotifyServiceDomainName, services, Helper.getNamingServiceInitial(services)); m_notifyServiceName = getNotificationFactoryName(); // log slow receiver error only every 30 seconds or every 100 times, whatever comes first receiverTooSlowLogRepeatGuard = new RepeatGuard(30, TimeUnit.SECONDS, 100, Logic.OR); eventHandlingExecutor = new ThreadPoolExecutor(0, 1, 1L, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(EVENT_QUEUE_CAPACITY), services.getThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); profiler = new StopWatch(); m_anyAide = new AnyAide(services); m_channelInfo = new ChannelInfo(services); m_handlerTimeoutMap = m_channelInfo.getEventHandlerTimeoutMap(channelName); // get the channel m_channel = getHelper().getNotificationChannel(m_notifyServiceName); // go ahead configured CORBA stuff createConsumer(); // if the developer has overriden these, they will subscribe to // subscriptions // automatically without having to call addSubscrib../addFilter n times // for each consumer subclass instance. configSubscriptions(); configFilters(); isTraceEventsEnabled = m_helper.getChannelProperties().isTraceEventsEnabled(m_channelName); // if the factory is null, the callback is not registered m_callback = new AcsNcReconnectionCallback(this, m_logger); m_callback.registerForReconnect(services, m_helper.getNotifyFactory()); // @TODO remove this hack once NC classes are integrated into container services try { if (services instanceof ContainerServicesImpl) { ContainerServicesImpl.CleanUpCallback cb = new ContainerServicesImpl.CleanUpCallback() { @Override public void containerServicesCleanUp() { disconnectLock.lock(); try { // @TODO distinguish between forgot to disconnect and failed to disconnect if (m_proxySupplier != null) { m_logger.warning("Looks like application code forgot to disconnect from channel " + m_channelName + " to clean up corba resources! ACS will do it now."); disconnect(); } } finally { disconnectLock.unlock(); } } }; ((ContainerServicesImpl)services).registerCleanUpCallback(cb); } } catch (Throwable thr) { m_logger.log(Level.WARNING, "Failed to set up the callback for automated resource cleanup", thr); } } /** * This method returns a constant character pointer to the notification * channel domain which 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 getChannelDomain() { return alma.acscommon.ALMADOMAIN.value; } /** * 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 accommodate subclasses which * subscribe/publish non-ICD style events (ACS archiving channel for * example). * In that case, the developer would override this method * (e.g. to return or logging channel with alma.acscommon.LOGGING_NOTIFICATION_FACTORY_NAME.value) * * @return string */ protected String getNotificationFactoryName() { return m_helper.getNotificationFactoryNameForChannel(); } /** * Handles the CORBA creation of a consumer. * Changed to private because only ctor of this class call this method as of Alma 5.0.2 * * @throws AcsJException * Any CORBA exceptions encountered are converted to an * AcsJException for developer's ease of use. */ private void createConsumer() throws AcsJException { IntHolder consumerAdminIDHolder = new IntHolder(); // get the Consumer admin object (no reuse of admin obj. This gets addressed in the new NCSubscriber class) // We don't need to use the TAO extension method "named_new_for_consumers" because only the proxy object will get a name from us. m_consumerAdmin = m_channel.new_for_consumers(InterFilterGroupOperator.AND_OP, consumerAdminIDHolder); // sanity check if (m_consumerAdmin == null) { String reason = "The '" + m_channelName + "' channel: null consumer admin"; throw new alma.ACSErrTypeJavaNative.wrappers.AcsJJavaLangEx(reason); } // get the Supplier proxy proxyID = new IntHolder(); gov.sandia.NotifyMonitoringExt.ConsumerAdmin consumerAdminExt = null; try { consumerAdminExt = gov.sandia.NotifyMonitoringExt.ConsumerAdminHelper.narrow(m_consumerAdmin); } catch (BAD_PARAM ex) { // Don't care, we won't be able to create the proxy with a name, but that's it // HSO: Actually this should never happen, because without TAO extension present, // already getting the NotifyFactory reference would have failed. } if( consumerAdminExt != null ) { // Add a random number to the clientName. This is due to the fact // that if we use a duplicate name to create a named proxy, // TAO will first create the proxy and later check for name duplication. // If duplication is found TAO will throw the NameAlreadyUsed exception, // (TAO scopes the proxy name to the EventChannel object, not to the consumer admin) // but won't actually destroy the newly created proxy on the server side, // which could lead to memory leaks while( m_proxySupplier == null ) { String randomizedClientName = m_helper.createRandomizedClientName(m_clientName); try { // Create the push supplier with a name m_proxySupplier = StructuredProxyPushSupplierHelper.narrow( consumerAdminExt.obtain_named_notification_push_supplier(ClientType.STRUCTURED_EVENT, proxyID, randomizedClientName)); m_logger.fine("Created named proxy supplier '" + randomizedClientName + "'"); } 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. } catch (NameMapError e) { // Default to the unnamed version try { m_proxySupplier = StructuredProxyPushSupplierHelper.narrow(m_consumerAdmin.obtain_notification_push_supplier(ClientType.STRUCTURED_EVENT, proxyID)); } catch (AdminLimitExceeded e1) { throw new AcsJCORBAProblemEx(e1); } } catch (AdminLimitExceeded e) { throw new AcsJCORBAProblemEx(e); } } } else { // Create the push supplier without a name try { m_proxySupplier = StructuredProxyPushSupplierHelper.narrow( m_consumerAdmin.obtain_notification_push_supplier(ClientType.STRUCTURED_EVENT, proxyID)); } catch (org.omg.CosNotifyChannelAdmin.AdminLimitExceeded e) { // convert it into an exception developers care about throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(e); } } // sanity check if (m_proxySupplier == null) { String reason = "The '" + m_channelName + "' channel: null proxy supplier"; throw new alma.ACSErrTypeJavaNative.wrappers.AcsJJavaLangEx(reason); } LOG_NC_SubscriptionConnect_OK.log(m_logger, m_channelName, m_notifyServiceName); } /** * After invoking this method, the user has no control over when * push_structured_event is invoked by the notification channel. User may * still add and remove subscriptions at any given time though. Also, the * connection can be suspended and resumed. * * Finally, consumerReady may spawn a thread to search for the channel if it * does not already exist. Once the channel becomes available, all saved * subscription are subscribed to and consumerReady is automatically invoked * again. * * @throws AcsJException * Thrown if the consumer cannot begin receiving events for some * CORBA reason. */ public void consumerReady() throws AcsJException { try { m_corbaRef = OSPushConsumerHelper.narrow(getHelper().getContainerServices().activateOffShoot(this)); m_proxySupplier.connect_structured_push_consumer(org.omg.CosNotifyComm.StructuredPushConsumerHelper.narrow(m_corbaRef)); } catch (AcsJContainerServicesEx e) { // convert it to an ACS Error System Exception throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(e); } catch (org.omg.CosEventChannelAdmin.AlreadyConnected e) { // Think there is virtually no chance of this every happening but... throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(e.getMessage()); } catch (org.omg.CosEventChannelAdmin.TypeError e) { // Think there is virtually no chance of this every happening but... throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(e.getMessage()); } } /** * Override this method to setup any number of event subscriptions. That is, * this method is invoked by consumer's constructor after everything else has * been initialized. */ protected void configSubscriptions() throws AcsJEventSubscriptionFailureEx{ // addSubscription(); return; } /** * Add a subscription to a given (IDL struct) Java class. Use this method * only when Consumer has been subclassed and processEvent overridden. * * @param structClass * Type of event to subscribe to (i.e., alma.CORR.DataStruct.class). * If <code>null</code> then all events are subscribed. * @throws AcsJEventSubscriptionFailureEx * Thrown if the subscription failed. */ public void addSubscription(Class<? extends IDLEntity> structClass) throws AcsJEventSubscriptionFailureEx { String type = "*"; String domain = "*"; if (structClass != null) { type = structClass.getSimpleName(); domain = getChannelDomain(); // "ALMA" } try { // Subscribe to events EventType[] added = { new EventType(domain, type) }; EventType[] removed = {}; // really subscribe to the events m_consumerAdmin.subscription_change(added, removed); } catch (Throwable thr) { // org.omg.CosNotifyComm.InvalidEventType or other AcsJEventSubscriptionFailureEx ex = new AcsJEventSubscriptionFailureEx(thr); ex.setChannelName(m_channelName); ex.setEventName(type); throw ex; } } /** * Add a subscription to a given (IDL struct) Java class and also register a * method capable of processing that event (IDL struct). Each time an event * of the (IDL struct) type is received, the Consumer will automatically * invoke the receiver object's "receive" method using the (IDL struct) data * extracted from the CORBA event. * * @param structClass * Type of event to subscribe to (i.e., alma.CORR.DataStruct.class). * @param receiver * An object which implements a method called "receive". The * "receive" method must accept an instance of a structClass * object as its sole parameter. * @throws AcsJException * Thrown if there is some CORBA problem. */ public void addSubscription(Class<? extends IDLEntity> structClass, Object receiver) throws AcsJException { // check to ensure receiver is capable to processing the event Class receiverClass = receiver.getClass(); Class[] parm = { structClass }; try { receiverClass.getMethod(RECEIVE_METHOD_NAME, parm); } catch (NoSuchMethodException err) { // Well the method doesn't exist...that sucks! String reason = "The '" + m_channelName + "' channel: the receiver object is incapable of handling '" + structClass.getName() + "' type of events! " + "It must have a method 'public void " + RECEIVE_METHOD_NAME + "(" + structClass.getName() + ")'."; m_logger.log(Level.WARNING, reason, err); throw new alma.ACSErrTypeJavaNative.wrappers.AcsJJavaLangEx(reason); } catch (SecurityException err) { // Developer has defined the method to be protected or private...this // doesn't work either. String reason = "The '" + m_channelName + "' channel: the receiver method of the object is protected/private for '" + structClass.getName() + "' type of events!"; m_logger.log(Level.WARNING, reason, err); throw new alma.ACSErrTypeJavaNative.wrappers.AcsJJavaLangEx(reason); } // Add this eventTypeName/receiver to the list of m_handlerFunctions. synchronized (m_handlerFunctions) { // make sure the developer has not subscribed to the same // event type with two different handlers. makes unsubscribing // easier (i.e., developer need not keep track of receiver // objects that would otherwise have to be passed // back to the removeSubscription method if (m_handlerFunctions.containsKey(structClass.getName())) { // throw an exception throw new AcsJJavaAnyEx("Type already subscribed to."); } m_handlerFunctions.put(structClass.getName(), receiver); } // Next call the real addSubscription method. this.addSubscription(structClass); } /** * Remove a subscription from this consumer. After invoking this, events of * the parameter's type will no longer be received. * * @param structClassName * Unsubscribes from this IDL struct (Java class). By passing in * null here, no events of any type will be received. * @throws AcsJException * Thrown if there is some CORBA problem (like this consumer has * never subscribed to the IDL struct). */ public void removeSubscription(Class structClass) throws AcsJException { String type = "*"; String domain = "*"; // If the developer is not unsubscribing from everything... if (structClass != null) { // get the type/domain to unsubscribe from type = structClass.getName().substring(structClass.getName().lastIndexOf('.') + 1); domain = getChannelDomain(); // Remove the handler function if there is one... synchronized (m_handlerFunctions) { if (m_handlerFunctions.containsKey(structClass.getName())) { // remove the subscription from the hash m_handlerFunctions.remove(structClass.getName()); } else { throw new AcsJJavaAnyEx("Unsubscribing from '" + structClass.getName() + "' type of event when not actually subscribed to this type."); } } } // they're removing all subscriptions so let's clear the hash else { m_handlerFunctions.clear(); } try { // Unsubscribe to events EventType[] added = {}; EventType[] removed = { new EventType(domain, type) }; // really unsubscribe from events m_consumerAdmin.subscription_change(added, removed); } catch (org.omg.CosNotifyComm.InvalidEventType e) { String msg = "'" + type + "' event type is invalid for the '" + m_channelName + "' channel: "; throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(msg + e.getMessage()); } } /** * Override this method in subclasses to specify all filters this Consumer * will use. The method would then just make a lot of calls to * addFilter(...). Important to note this is the last method invoked by * consumer's constructor. */ protected void configFilters() { return; } /** * Adds a single filter to this consumer. With ALMA's use of IDL structs for * ICD events, this method is no longer useful. May become deprecated in * future ACS releases. * * @return The filter ID of the newly created filter. This is only useful for * removing filters. Returns -1 on failure. * @param structClassName * IDL struct Java class filter is to be applied to. * @param filter * The filter string in extended trader constraint language. * @throws AcsJException * Thrown if there is some CORBA problem (like the filter is not * using the correct grammar). */ public int addFilter(Class<? extends IDLEntity> structClassName, String filter) throws AcsJException { String type = structClassName.getName().substring(structClassName.getName().lastIndexOf('.') + 1); try { FilterFactory t_filterFactory = m_channel.default_filter_factory(); // create the filter Filter t_filter = t_filterFactory.create_filter(getFilterLanguage()); EventType[] t_eType = { new EventType(getChannelDomain(), type) }; ConstraintExp[] t_cexp = { new ConstraintExp(t_eType, filter) }; t_filter.add_constraints(t_cexp); // add the filter to the proxy return m_proxySupplier.add_filter(t_filter); } catch (org.omg.CosNotifyFilter.InvalidGrammar e) { String msg = "'" + filter + "' filter is invalid for the '" + m_channelName + "' channel: "; throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(msg + e.getMessage()); } catch (org.omg.CosNotifyFilter.InvalidConstraint e) { String msg = "'" + filter + "' filter is invalid for the '" + m_channelName + "' channel: "; throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(msg + e.getMessage()); } } /** * Removes a single filter from this consumer. See deprecation comments on * the addFilter method. * * @param filter * The filter's unique ID. This parameter is the return value of * the addFilter method. * @throws AcsJException * Thrown if CORBA problems are encountered. * @return True if the filter was succesfully removed and false otherwise. * TODO: this method should not throw an AcsJException */ public boolean removeFilter(int filter) throws AcsJException { // not a real filter in the first place if (filter == -1) { String msg = "Cannot remove the '" + filter + "' filter ID for the '" + m_channelName + "' channel: bad filter ID!"; m_logger.warning(msg); return false; } try { m_proxySupplier.remove_filter(filter); return true; } catch (org.omg.CosNotifyFilter.FilterNotFound e) { String msg = "Cannot remove the '" + filter + "' filter ID for the '" + m_channelName + "' channel: filter not found!"; m_logger.warning(msg); return false; } } /** * This method returns a string to the type of filter constraint language to * be used for filtering events which is normally equivalent to * acsnc::FILTER_LANGUAGE_NAME. Override to change this behavior. * * @return pointer to a constant string. */ protected String getFilterLanguage() { // return a constant defined in acsnc.idl to be portable in the other // programming languages supported by ACS. return alma.acsnc.FILTER_LANGUAGE_NAME.value; } /** * Override this method to do some "housekeeping". Invoked by the CORBA * Notification Service itself each time a Supplier subclass is destroyed. * <b>Do not call it from your code!</b> */ public void disconnect_structured_push_consumer() { // ACS does not provide an implementation of this method. developers // are free to override it if they want though. } /** * As of ACS 3.0, override {@link #processEvent(Object)} instead of this * CORBA method. push_structured_event is what is called each time an event * is received. <b>Do not call it from your code!</b> * * @param structuredEvent * The structured event sent by a supplier subclass. * @throws org.omg.CosEventComm.Disconnected */ public void push_structured_event(StructuredEvent structuredEvent) throws org.omg.CosEventComm.Disconnected { // time to get the event description final EventDescription eDescrip = EventDescriptionHelper.extract(structuredEvent.remainder_of_body); Object convertedAny = m_anyAide.complexAnyToObject(structuredEvent.filterable_data[0].value); IDLEntity struct = null; try { struct = (IDLEntity) convertedAny; if (isTraceEventsEnabled) { m_logger.log(Level.INFO, "Channel:" + m_channelName + ", Publisher:" + eDescrip.name + ", Event Type:" + structuredEvent.header.fixed_header.event_type.type_name); } } catch (ClassCastException ex) { if (isTraceEventsEnabled && convertedAny != null) { m_logger.log(Level.INFO, "Channel:" + m_channelName + ", Publisher:" + eDescrip.name + ", Event Type:" + structuredEvent.header.fixed_header.event_type.type_name + ". Failed to convert event data of type '" + convertedAny.getClass().getName() + "' which is not derived from an IDL-defined struct."); } } if (struct != null) { // process the extracted data in a separate thread final IDLEntity structToProcess = struct; // to avoid unnecessary scary logs, we tolerate previous events up to half the queue size boolean isReceiverBusyWithPreviousEvent = ( eventHandlingExecutor.getQueue().size() > EVENT_QUEUE_CAPACITY / 2 ); // m_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(structToProcess, eDescrip); } }); } catch (RejectedExecutionException ex) { // receivers have been too slow, queue is full, will drop data. thisEventDiscarded = true; numEventsDiscarded++; } if ( (thisEventDiscarded || isReceiverBusyWithPreviousEvent) && receiverTooSlowLogRepeatGuard.checkAndIncrement()) { LOG_NC_ReceiverTooSlow.log(m_logger, m_clientName, numEventsDiscarded, struct.getClass().getName(), m_channelName, getNotificationFactoryName()); numEventsDiscarded = 0; } } else { // @todo (HSO) unclear why we ignore events w/o data attached. At least now we log it so people can complain. // Should compare this with specs and C++ impl if (isTraceEventsEnabled) { m_logger.info("Will ignore event of type " + structuredEvent.header.fixed_header.event_type.type_name + " which has no data attached."); } } } /** * The method invoked each time an ICD-style event is received. Consumer * subclasses <B>must</B> override this method or the other signature of * processEvent (if they do not care about the EventDescription parameter). * If receiver object(s) have been registered using the addSubscription * method, this method does not need to be overriden. * * @param corbaData * Generally an IDL struct. It has already been extracted from the * CORBA Any (when it was packed into the CORBA structured event) * and a simple typecast on it to the correct type should be enough * to make it usable. * @param eventDescrip * An instance of an IDL struct which describes the event. See * acsnc.idl for the defintion. */ protected void processEvent(IDLEntity corbaData, EventDescription eventDescrip) { m_logger.finer("Consumer#processEvent received an event of type " + eventDescrip.name); // Create the IDL class // The only reason we have to do this is in case the developer has // created // and registered a receiver object with this instance. Class[] parm = { corbaData.getClass() }; // event name String eventName = corbaData.getClass().getName(); // figure out how much time this event has to be processed if (!m_handlerTimeoutMap.containsKey(eventName)) { // setup a timeout if it's undefined m_handlerTimeoutMap.put(eventName, DEFAULT_MAX_PROCESS_TIME); } Double maxProcessTimeDouble = m_handlerTimeoutMap.get(eventName); long maxProcessTime = (long) maxProcessTimeDouble.doubleValue(); // @TODO: the cast is stupid if time is actually given in fractional seconds. // Here we search the hash of registered receiver objects. // If a receiver capable of processing this event is found, it is invoked. // Otherwise the developer should have overriden the process event method. if (m_handlerFunctions.containsKey(eventName)) { try { // get the receive method Method handlerFunction = m_handlerFunctions.get(eventName).getClass().getMethod( RECEIVE_METHOD_NAME, parm); // get the parameters of this method... Class[] tArray = handlerFunction.getParameterTypes(); // if the first parameter to the receiver method is identical // to the Java class... if (tArray[0].equals(corbaData.getClass())) { // good...user has registered a receiver // parameters are the IDL struct Object[] arg = { corbaData }; // finally we can invoke the "receive" method on the receiver object and start the timing profiler.reset(); handlerFunction.invoke(m_handlerFunctions.get(eventName), arg); // get the execution time of 'receive'. // @TODO: it looks like a bug to compare profiled milliseconds with fractional seconds, // unless the comments in ChannelInfo about time given in seconds are wrong! long timeToRun = profiler.getLapTimeMillis(); // warn the end-user if the receiver is taking too long if (timeToRun > maxProcessTime) { m_logger.warning("Took too long to handle an '" + eventName + "' event: " + timeToRun / 1000.0 + " seconds."); m_logger.info("Maximum time to process an event is: " + maxProcessTime / 1000.0 + " seconds."); } // everything looks OK...return control. return; } } catch (InvocationTargetException ex) { LOG_NC_EventReceive_HandlerException.log(m_logger, m_channelName, getNotificationFactoryName(), eventName, m_handlerFunctions.get(eventName).getClass().getName(), ex.getCause().toString()); } catch (Exception e) { m_logger.log(AcsLogLevel.DEBUG, "Unexpected exception during event handling.", e); } } else { //m_logger.fine("Did not find a handler for event " + eventName); } // If this isn't overriden, just pass it on down the chain. profiler.reset(); this.processEvent(corbaData); long timeToRun = profiler.getLapTimeMillis(); if (timeToRun > maxProcessTime) { m_logger.warning("Took too long to handle an '" + eventName + "' event: " + timeToRun / 1000.0 + " seconds."); m_logger.info("Maximum time to process an event is: " + maxProcessTime / 1000.0 + " seconds."); } } /** * The method invoked each time an ICD-style event is received. Consumer * subclasses <B>must</B> override this method. If receiver object(s) have * been registered using the addSubscription method, this method does not * need to be overriden. * * @param corbaData * Generally an IDL struct. It has already been extracted from the * CORBA Any (when it was packed into the CORBA structured event) * and a simple typecast on it to the correct type should be enough * to make it usable. */ protected void processEvent(Object corbaData) { String msg = "Consumer.processEvent(...) the '" + m_channelName + "' channel: " + "override this method in derived classes for '" + corbaData.getClass().getName() + "' objects!"; m_logger.finest(msg); } /** * A "smart" consumer will override this method to subscribe to new * domain/type events as suppliers offer them. A fairly advanced feature. * <b>Do not call it from your code!</b> * * @param eventType * Domain/type pairs of events that have been added to this * channel. * @param eventType1 * Domain/type pairs of events that have been removed from this * channel. * @throws org.omg.CosNotifyComm.InvalidEventType * ... */ public void offer_change(EventType[] eventType, EventType[] eventType1) throws org.omg.CosNotifyComm.InvalidEventType { // ACS does not provide an implementation of this method although // developers are free to do so if they like. } /** * This method <B>must</B> be invoked before a component or client is * destroyed. Failure to do so can cause remote memory leaks. Make sure it is * not invoked multiple times. Once it has been called, events will no longer * be received. */ public void disconnect() { disconnectLock.lock(); boolean success = false; try { // do better than NPE if someone actually calls this twice if (m_proxySupplier == null) { throw new IllegalStateException("Consumer already disconnected."); } // stop receiving events suspend(); // remove all filters m_proxySupplier.remove_all_filters(); // remove all subscriptions // DWF-fix me! removeSubscription(null); // handle notification channel cleanup m_proxySupplier.disconnect_structured_push_supplier(); m_consumerAdmin.destroy(); // shut down the event queue eventHandlingExecutor.shutdown(); boolean queueOK = eventHandlingExecutor.awaitTermination(2, TimeUnit.SECONDS); if (!queueOK) { // timeout occured, may still have events in the queue. Terminate with error message int remainingEvents = eventHandlingExecutor.getQueue().size(); m_logger.info("Disconnecting from NC '" + m_channelName + "' before all events have been processed, in spite of 2 s timeout grace period. " + remainingEvents+ " events are still in the queue and may continue to be processed by the receiver."); } // clean-up CORBA stuff m_callback.disconnect(); if (m_corbaRef != null) { getHelper().getContainerServices().deactivateOffShoot(this); } m_logger.finer("Disconnected from NC '" + m_channelName + "'."); success = true; } catch (org.omg.CORBA.OBJECT_NOT_EXIST ex1) { // this is OK, because someone else has already destroyed the remote resources m_logger.fine("No need to release resources for channel " + m_channelName + " because the NC has been destroyed already."); success = true; } catch (Exception ex2) { m_logger.log(Level.WARNING, "Failed to disconnect from NC '" + m_channelName + "'.\n" + ex2.toString()); } finally { if (success) { // null the refs if everything was fine, or if we got the OBJECT_NOT_EXIST m_callback = null; m_corbaRef = null; m_consumerAdmin = null; m_proxySupplier = null; } disconnectLock.unlock(); } } /** * Used to temporarily halt receiving events of all types. * <p> * If the consumer has been connected already (method {@link #consumerReady()}, * then after calling this method, incoming events will be buffered instead of being discarded; * unexpired events will be received later, after a call to {@link #resume()}. <br> * This design follows CORBA NC standard, as described in * <it>Notification Service Specification, Version 1.1, formal/04-10-11, 3.4.13 The StructuredProxyPushSupplier Interface.</it> */ public void suspend() { disconnectLock.lock(); try { // do better than NPE if someone actually calls this twice if (m_proxySupplier == null) { throw new IllegalStateException("Consumer already disconnected"); } try { m_proxySupplier.suspend_connection(); } catch (org.omg.CosNotifyChannelAdmin.ConnectionAlreadyInactive e) { // if this fails, it does not matter because the connection // has already been suspended. } catch (org.omg.CosNotifyChannelAdmin.NotConnected e) { // if this fails, it does not matter because we cannot suspend // a connection that isn't really connected in the first place. } } finally { disconnectLock.unlock(); } } /** * Used to reenable the Consumer after a call to the <code>suspend()</code> method. * Queued events will be received after this call, see {@link #suspend()}. * <p> * This call has no effect if the consumer is not connected at all (see {@link #consumerReady()}), * or if it has not been suspended. */ public void resume() { try { m_proxySupplier.resume_connection(); } catch (org.omg.CosNotifyChannelAdmin.ConnectionAlreadyActive e) { // if this fails, it does not matter because the connection // has already been resumed. } catch (org.omg.CosNotifyChannelAdmin.NotConnected e) { // if this fails, it does not matter because we cannot resume // a connection that isn't connected in the first place. } } /** * Returns a reference to this instance's helper. Not too useful outside this * class. * * @return A valid reference to this instances helper. */ public alma.acs.nc.Helper getHelper() { return m_helper; } @Override public void reconnect(EventChannelFactory ecf) { if (m_channel != null) m_channel = m_helper.getNotificationChannel(ecf); if (m_channel == null) m_logger.log(Level.WARNING, "Cannot reconnect to the channel: " + m_channelName); try { m_channel.set_qos(m_helper.getChannelProperties(). getCDBQoSProps(m_channelName)); m_channel.set_admin(m_helper.getChannelProperties(). getCDBAdminProps(m_channelName)); } catch (UnsupportedQoS e) { } catch (AcsJException e) { } catch (UnsupportedAdmin ex) { m_logger.warning(m_helper.createUnsupportedAdminLogMessage(ex)); } catch (NullPointerException e) { } } }