/* * 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 * * Helper.java * * Created on March 4, 2003, 10:41 AM * * This class is intended to be a prototype for the Java notification channel * based in part on the C++ Helper class in acsnc. */ package alma.acs.nc; import java.util.HashMap; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import org.omg.CORBA.BAD_PARAM; import org.omg.CORBA.IntHolder; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContext; import org.omg.CosNaming.NamingContextHelper; import org.omg.CosNotification.Property; import org.omg.CosNotification.PropertyError; import org.omg.CosNotification.UnsupportedAdmin; import org.omg.CosNotification.UnsupportedQoS; import org.omg.CosNotifyChannelAdmin.ChannelNotFound; import com.cosylab.CDB.DAO; import com.cosylab.cdb.client.CDBAccess; import com.cosylab.cdb.client.DAOProxy; import com.cosylab.util.WildcharMatcher; import gov.sandia.NotifyMonitoringExt.EventChannel; import gov.sandia.NotifyMonitoringExt.EventChannelFactory; import gov.sandia.NotifyMonitoringExt.EventChannelFactoryHelper; import gov.sandia.NotifyMonitoringExt.EventChannelHelper; import gov.sandia.NotifyMonitoringExt.NameAlreadyUsed; import gov.sandia.NotifyMonitoringExt.NameMapError; import alma.ACSErrTypeCORBA.wrappers.AcsJNarrowFailedEx; import alma.ACSErrTypeCommon.wrappers.AcsJBadParameterEx; import alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx; import alma.ACSErrTypeCommon.wrappers.AcsJGenericErrorEx; import alma.ACSErrTypeCommon.wrappers.AcsJIllegalArgumentEx; import alma.ACSErrTypeCommon.wrappers.AcsJUnexpectedExceptionEx; import alma.AcsNCTraceLog.LOG_NC_ChannelCreatedRaw_OK; import alma.AcsNCTraceLog.LOG_NC_ChannelCreated_ATTEMPT; import alma.AcsNCTraceLog.LOG_NC_ChannelCreated_OK; import alma.AcsNCTraceLog.LOG_NC_ChannelDestroyed_OK; import alma.AcsNCTraceLog.LOG_NC_TaoExtensionsSubtypeMissing; import alma.acs.container.AdvancedContainerServices; import alma.acs.container.ContainerServicesBase; import alma.acs.exceptions.AcsJException; import alma.acs.logging.AcsLogLevel; import alma.acs.util.StopWatch; import alma.acscommon.ACS_NC_DOMAIN_ARCHIVING; import alma.acscommon.ACS_NC_DOMAIN_LOGGING; import alma.acscommon.ARCHIVE_NOTIFICATION_FACTORY_NAME; import alma.acscommon.LOGGING_NOTIFICATION_FACTORY_NAME; import alma.acscommon.NAMESERVICE_BINDING_NC_DOMAIN_DEFAULT; import alma.acscommon.NAMESERVICE_BINDING_NC_DOMAIN_SEPARATOR; import alma.acscommon.NC_KIND; /** * This class provides methods useful to both supplier and consumer objects, * where it is associated with exactly one Notification Channel. * It can also be used without any notification channel. * Never use a Helper instance for more than once NC! */ public class Helper { /** * In a running system, there can be only one reference to the Naming Service. */ private final NamingContext m_nContext; /** * Java property name for the CORBA Naming Service corbaloc. * This property is set in the acsstartup :: acsStartJava. * <p> * @TODO: Check if we can eliminate this property if we use something similar as in * jmanager's {@link com.cosylab.acs.maci.plug.NamingServiceRemoteDirectory}. */ private static final String m_nameJavaProp = "ORBInitRef.NameService"; /** * Access to the component's name along with the logging service. */ private final ContainerServicesBase m_services; // Our own personal logger protected final Logger m_logger; /** * A Helper instance serves only one NC. */ protected final String channelName; /** * The optional NC domain name, e.g. "ALARMSYSTEM". */ protected final String domainName; /** * channelId is used to reconnect to channel in case that Notify Server crashes */ private int channelId; // Provides access to channel's quality of service properties private final ChannelProperties m_channelProperties; /** * Cached notify factory name where our NC is or should be hosted. * Avoids unnecessary CDB calls. */ private volatile String ncFactoryName; /** * TODO */ private EventChannelFactory notifyFactory; /** * timestamp in ms when config error was found. Initialized to 0L. * <p> * Used similar to a log repeat guard, because the OMC was flooded with NC config problem logs * (as of 2008-03-06). */ private Long channelConfigProblemTimestamp = 0L; /** * Random number generator, used to randomize client names * when used as server-side proxy names. * This class is static and thus shared among the subscribers and suppliers of one client or component, * to reduce the risk that two subscribers or suppliers create * Helper instances at the same time (system time is used as random generator seed) * and thereby produce identical sequences of random numbers, * which would defy the purpose of avoiding name clashes on the server. */ protected static final Random random = new Random(System.currentTimeMillis()); public static String extractChannelName(String channelWithDomainName) throws AcsJIllegalArgumentEx { return splitChannelAndDomainName(channelWithDomainName)[0]; } public static String extractDomainName(String channelWithDomainName) throws AcsJIllegalArgumentEx { return splitChannelAndDomainName(channelWithDomainName)[1]; } private static String[] splitChannelAndDomainName(String channelWithDomainName) throws AcsJIllegalArgumentEx { String[] ret = channelWithDomainName.split(NAMESERVICE_BINDING_NC_DOMAIN_SEPARATOR.value); if (ret.length == 2) { return ret; } else { AcsJIllegalArgumentEx ex = new AcsJIllegalArgumentEx(); ex.setVariable("channelWithDomainName"); ex.setValue(channelWithDomainName); ex.setErrorDesc("No unique NC - domain separation found in NC binding."); throw ex; } } public static String combineChannelAndDomainName(String channelName, String domainName) { String ret = channelName; ret += NAMESERVICE_BINDING_NC_DOMAIN_SEPARATOR.value; if (domainName != null) { ret += domainName; } else { ret += NAMESERVICE_BINDING_NC_DOMAIN_DEFAULT.value; } return ret; } /** * Same as {@link #Helper(String, String, ContainerServicesBase, NamingContext)} but without the NC domain; * the default NC domain ({@link NAMESERVICE_BINDING_NC_DOMAIN_DEFAULT.value}) will be used. * * @param channelName * @param services * @param namingService * @throws AcsJException */ public Helper(String channelName, ContainerServicesBase services, NamingContext namingService) throws AcsJException { this(channelName, null, services, namingService); } /** * Creates a new instance of Helper. * @param channelName The NC that this Helper is made for. * @param domainName The optional NC domain, or <code>null</code>. * @param services A reference to the ContainerServices * @param namingService Reference to the naming service. * @throws AcsJException Generic ACS exception will be thrown if anything in this class is broken. */ public Helper(String channelName, String domainName, ContainerServicesBase services, NamingContext namingService) throws AcsJException { this.channelName = channelName; this.domainName = domainName; if (services == null) { AcsJBadParameterEx ex = new AcsJBadParameterEx(); ex.setReason("Null reference obtained for the ContainerServices!"); throw ex; } // save a local reference to the container services m_services = services; // immediately grab a logger m_logger = m_services.getLogger(); // create a helper object used to retrieve channel properties m_channelProperties = new ChannelProperties(services); m_nContext = namingService; } /** * Returns a reference to the Container Services which are provided by a * container or client. * * @return A valid reference to the ContainerServices instance. */ public ContainerServicesBase getContainerServices() { return m_services; } /** * <b>This method should only be used by specialized framework code, * not by application code that uses NCs.</b> * It retrieves and returns a reference to the Naming Service based on the * Java property <code>ORBInitRef.NameService</code>. * <p> * Even specialized code that has an AcsManagerProxy instance available, * e.g. via an AdvancedComponentClient, * should use the AcsManagerProxy to get the NamingContext reference: * <pre> * nctx = NamingContextHelper.narrow(AdvancedComponentClient.getAcsManagerProxy().get_service("NameService", false); * </pre>. * <p> * * @return Valid reference to the Naming Service. * @throws AcsJException * Thrown when there's a bad corbaloc given for the Naming Service * or the reference cannot be narrowed. * @see #getNamingService() * @deprecated To be removed once we have fixed the leftover usages in modules * jcontnc, laser-core, acssamp, acssampGUI. */ public static NamingContext getNamingServiceInitial(ContainerServicesBase cs) throws AcsJException { // acsStartJava always adds this Java property for us String nameCorbaloc = System.getProperty(m_nameJavaProp); // make sure the end-user is using acsStartJava if (nameCorbaloc == null || nameCorbaloc.trim().isEmpty()) { AcsJBadParameterEx ex = new AcsJBadParameterEx(); ex.setReason("Missing Java property '" + m_nameJavaProp + "' for the Naming Service corbaloc!"); throw ex; } // get the unnarrowed reference to the Naming Service org.omg.CORBA.Object tempCorbaObject = cs.getAdvancedContainerServices().corbaObjectFromString(nameCorbaloc); if (tempCorbaObject == null) { // very bad situation. without the naming service we cannot do anything. Throwable cause = new Throwable("Null reference obtained for the Naming Service, corbaloc=" + nameCorbaloc); //AcsJFailedToResolveServiceEx ex = new AcsJFailedToResolveServiceEx(); // @TODO add parameter "service" and "reason" to definition of AcsJFailedToResolveServiceEx // ex.setReason("Null reference obtained for the Naming Service, corbaloc=" + nameCorbaloc); throw new alma.ACSErrTypeCORBA.wrappers.AcsJFailedToResolveServiceEx(cause); } NamingContext ret = NamingContextHelper.narrow(tempCorbaObject); if (ret == null) { // very bad situation. without the naming service we cannot do anything. Throwable cause = new Throwable("Unable to narrow Naming Service reference to the correct type!"); throw new alma.ACSErrTypeCommon.wrappers.AcsJTypeNotSupportedEx(cause); } return ret; } /** * Returns a reference to the Naming Service. * <p> * @return Valid reference to the Naming Service. */ public NamingContext getNamingService() { return m_nContext; } /** * The TAO extension's reconnect() methods call this (via NCSubscriber etc), * so that we call again {@link org.omg.CosNotifyChannelAdmin.EventChannelFactoryOperations#get_event_channel(int)}. */ public EventChannel getNotificationChannel(EventChannelFactory ecf) { EventChannel ec = null; try { ec = EventChannelHelper.narrow( ecf.get_event_channel(channelId) ); } catch (ChannelNotFound e) { // I cannot recover the channel using the ID } return ec; } /** * This method gets a reference to the event channel. If it is not already * registered with the naming service, it is created. * * @return Reference to the event channel specified by channelName. Never null. * @param channelKind * Kind of the channel as registered with the CORBA naming service ("channels"). * @param notifyFactoryName * Name of the notification service as registered with the CORBA * naming service. * @throws AcsJException * Standard ACS Java exception. */ protected EventChannel getNotificationChannel(String notifyFactoryName) throws AcsJException { String channelKind = NC_KIND.value; // return value EventChannel retValue = null; NameComponent[] t_NameSequence = { new NameComponent(combineChannelAndDomainName(channelName, domainName), channelKind) }; // (retryNumberAttempts * retrySleepSec) = the time before we give up to get a reference or create the channel if // a channel of the given name supposedly gets created already (due to race conditions with other clients). int retryNumberAttempts = 20; int retrySleepSec = 2; do { try { // @TODO move the check for existing channel from naming service to the NC factory, // now that we use the TAO extension with named NCs. // The only advantage of still using the naming service is that the naming service is a real system-wide singleton // and can return also channels that were by mistake created from a different notify service factory than the one configured in the CDB. initializeNotifyFactory(notifyFactoryName); retValue = EventChannelHelper.narrow(getNamingService().resolve(t_NameSequence)); } catch (org.omg.CosNaming.NamingContextPackage.NotFound e) { // No other consumers or suppliers have registered the channel yet... // This can mean that the channel has never been created, or that it is currently being created but has not yet been registered. } catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) { // Think there is virtually no chance of this every happening but... throw new AcsJUnexpectedExceptionEx(e); } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) { // Think there is virtually no chance of this every happening but... throw new AcsJUnexpectedExceptionEx(e); } if (retValue == null) { // Couldn't get the channel object from the naming service. // Let's try to create it, which may fail if some other consumer or supplier is currently doing the same, // but only because we use the TAO extensions that support named NCs. try { retValue = createNotificationChannel(channelKind, notifyFactoryName); } catch (NameAlreadyUsed ex) { m_logger.log(Level.INFO, "NC '" + channelName + "' seems to be getting created. Will wait and try again in " + retrySleepSec + " seconds.", ex); try { Thread.sleep(retrySleepSec*1000); } catch (InterruptedException ex1) { // too bad } } } // else { // System.out.println("*** Got NC " + channelName + " from the naming service"); // } } while (retValue == null && --retryNumberAttempts >= 0); if (retValue == null) { AcsJGenericErrorEx ex = new AcsJGenericErrorEx(); ex.setErrorDesc("Giving up to get reference to channel " + channelName); throw ex; } return retValue; } /** * @param notifyFactoryName * @throws AcsJException AcsJCORBAProblemEx if the NotifyService reference cannot be retrieved from the NamingService; * AcsJNarrowFailedEx if the NotifyService is not of the required TAO extension type. */ protected void initializeNotifyFactory(String notifyFactoryName) throws AcsJException { if (notifyFactory == null){ final String standardEventFactoryId = org.omg.CosNotifyChannelAdmin.EventChannelFactoryHelper.id(); final String specialEventFactoryId = gov.sandia.NotifyMonitoringExt.EventChannelFactoryHelper.id(); // get the Notification Factory first. NameComponent[] t_NameFactory = { new NameComponent(notifyFactoryName, "") }; org.omg.CORBA.Object notifyFactoryObj = null; //notifyFactory = null; try { notifyFactoryObj = getNamingService().resolve(t_NameFactory); } catch (org.omg.CosNaming.NamingContextPackage.NotFound ex) { String reason = "The CORBA Notification Service '" + notifyFactoryName + "' is not registered in the Naming Service: " + ex.why.toString(); AcsJCORBAProblemEx ex2 = new AcsJCORBAProblemEx(); ex2.setInfo(reason); throw ex2; } catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } // narrow the notification factory to the TAO extension subtype try { notifyFactory = EventChannelFactoryHelper.narrow(notifyFactoryObj); } catch (BAD_PARAM ex) { if (notifyFactoryObj._is_a(standardEventFactoryId)) { LOG_NC_TaoExtensionsSubtypeMissing.log(m_logger, notifyFactoryName, specialEventFactoryId, standardEventFactoryId); } else { LOG_NC_TaoExtensionsSubtypeMissing.log(m_logger, notifyFactoryName, specialEventFactoryId, "???"); } AcsJNarrowFailedEx ex2 = new AcsJNarrowFailedEx(ex); ex2.setNarrowType(specialEventFactoryId); throw ex2; } } } /** * Tries to create a notification channel (using quality of service and administrative properties * specified by configQofS() and configAdminProps() respectively). * If this succeeds, then registers this channel with the naming service. * <p> * Should only be invoked when the channel that this supplier or consumer is attempting to connect to * does not exist. * However even with prior check for the existence of this channel, a race condition with other suppliers or consumers * can lead to multiple attempts to create the same channel, which will result in <code>NameAlreadyUsed</code> exception. * <p> * Design note: Currently the TAO notification extensions are used to synch channel creation with other clients * by supplying the channel name to the factory. * If we want to use only standard NC factories then we'd have to implement our own locking mechanisms in all * ACS consumer and supplier classes, see http://jira.alma.cl/browse/COMP-2808 * * @return Reference to the newly created channel. * @param channelKind * Kind of the channel as registered with the CORBA naming service. * @param notifyFactoryName * Name of the notification service as registered with the CORBA naming service. * @throws AcsJException * Standard ACS Java exception. * @throws NameAlreadyUsed thrown if the channel of this name already exists. */ protected EventChannel createNotificationChannel(String channelKind, String notifyFactoryName) throws AcsJException, NameAlreadyUsed { LOG_NC_ChannelCreated_ATTEMPT.log(m_logger, channelName, notifyFactoryName); // return value EventChannel retValue = null; channelId = -1; // to be assigned by factory StopWatch stopwatch = new StopWatch(); try { initializeNotifyFactory(notifyFactoryName); // create the channel // here we use the channel properties taken directly from our channel properties helper object. // presumably these values come from the ACS configuration database. IntHolder channelIdHolder = new IntHolder(); retValue = createNotifyChannel_internal( m_channelProperties.configQofS(channelName), m_channelProperties.configAdminProps(channelName), channelIdHolder); // The fact that we got here without exception means that the channel name was not used yet. // sanity check if (retValue == null) { // a null reference implies we cannot go any further Throwable cause = new Throwable("Null reference obtained for the '" + channelName + "' channel!"); throw new alma.ACSErrTypeJavaNative.wrappers.AcsJJavaLangEx(cause); // TODO: more specific ex type } channelId = channelIdHolder.value; // register our new channel with the naming service try { NameComponent[] t_NameChannel = { new NameComponent( combineChannelAndDomainName(channelName, domainName), channelKind) }; getNamingService().rebind(t_NameChannel, retValue); } catch (org.omg.CosNaming.NamingContextPackage.NotFound ex) { // Corba spec: "If already bound, the previous binding must be of type nobject; // otherwise, a NotFound exception with a why reason of not_object is raised." String reason = "Failed to register the new channel '" + channelName + "' with the Naming Service: " + ex.why.toString(); AcsJCORBAProblemEx ex2 = new AcsJCORBAProblemEx(ex); ex2.setInfo(reason); throw ex2; } } catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } catch (org.omg.CosNotification.UnsupportedQoS e) { Throwable cause = new Throwable("The quality of service properties specified for the '" + channelName + "' channel are unsupported: " + e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } LOG_NC_ChannelCreated_OK.log(m_logger, channelName, channelId, notifyFactoryName, stopwatch.getLapTimeMillis()); return retValue; } /** * Broken out from {@link #createNotificationChannel(String, String, String)} * to give tests better control about the timing when this call to the event factory is made. * @throws NameAlreadyUsed if the call to NotifyFactory#create_named_channel fails with this exception. * @throws AcsJCORBAProblemEx if the TAO extension throws a NameMapError or if the QoS attributes cause a UnsupportedAdmin. */ protected EventChannel createNotifyChannel_internal(Property[] initial_qos, Property[] initial_admin, IntHolder channelIdHolder) throws NameAlreadyUsed, UnsupportedQoS, AcsJNarrowFailedEx, AcsJCORBAProblemEx { EventChannel ret = null; StopWatch stopwatch = new StopWatch(); try { // The TAO extension of the notify factory that we use declares only the plain EventChannel type, // even though it creates the TAO-extension subtype. org.omg.CosNotifyChannelAdmin.EventChannel eventChannelBaseType = notifyFactory.create_named_channel( initial_qos, initial_admin, channelIdHolder, channelName); LOG_NC_ChannelCreatedRaw_OK.log(m_logger, channelName, channelIdHolder.value, stopwatch.getLapTimeMillis()); // re-create the client side corba stub, to get the extension subtype ret = gov.sandia.NotifyMonitoringExt.EventChannelHelper.narrow(eventChannelBaseType); } catch (BAD_PARAM ex) { LOG_NC_TaoExtensionsSubtypeMissing.log(m_logger, channelName, EventChannel.class.getName(), org.omg.CosNotifyChannelAdmin.EventChannelHelper.id()); AcsJNarrowFailedEx ex2 = new AcsJNarrowFailedEx(ex); ex2.setNarrowType(EventChannelHelper.id()); throw ex2; } catch (NameMapError ex) { String msg = "Got a TAO extension-specific NameMapError exception that means the TAO NC extension is not usable. Bailing out since we need the extension."; m_logger.log(AcsLogLevel.ERROR, msg, ex); AcsJCORBAProblemEx ex2 = new AcsJCORBAProblemEx(ex); ex2.setInfo(msg); throw ex2; } catch (UnsupportedAdmin ex) { AcsJCORBAProblemEx ex2 = new AcsJCORBAProblemEx(ex); ex2.setInfo(createUnsupportedAdminLogMessage(ex)); throw ex2; } return ret; } /** * <b>Destroys the channel and unregisters it from the naming service. ONLY * USE THIS METHOD IF YOU KNOW FOR CERTAIN THERE IS ONLY ONE SUPPLIER FOR THE * CHANNEL!!! Use this method with extreme caution as it's likely to become * deprecated in future versions of ACS!</b> * * @param channelName * name of the channel as registered int the CORBA notification * service * @param channelKind * Kind of the channel as registered with the CORBA naming service. * @param channelRef * reference to the channel being destroyed. * Here we use the plain OMG type instead of the TAO extension subtype, because the extensions are * not used for channel destruction. * @throws AcsJException * Thrown when the channel isn't registered with the Naming * Service. * @warning this method assumes */ protected void destroyNotificationChannel(String channelKind, org.omg.CosNotifyChannelAdmin.EventChannel channelRef) throws AcsJException { try { // destroy the remote CORBA object channelRef.destroy(); // unregister our channel with the naming service NameComponent[] t_NameChannel = { new NameComponent( combineChannelAndDomainName(channelName, domainName), channelKind) }; getNamingService().unbind(t_NameChannel); } catch (org.omg.CosNaming.NamingContextPackage.NotFound e) { // Think there is virtually no chance of this every happening but... String msg = "Cannot unbind the '" + channelName + "' channel from the Naming Service!"; m_logger.severe(msg); } catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) { // Think there is virtually no chance of this every happening but... Throwable cause = new Throwable(e.getMessage()); throw new alma.ACSErrTypeCommon.wrappers.AcsJCORBAProblemEx(cause); } LOG_NC_ChannelDestroyed_OK.log(m_logger, channelName, ""); // TODO use notif.service name } /** * Provides access to the information about the channel contained within the * ACS CDB * * @return This class's channel properties member. */ public ChannelProperties getChannelProperties() { return m_channelProperties; } /** * Gets the notification channel factory name for the given channel/domain of this Helper class. * <p> * Details: * <ul> * <li>First tries to use the factory name cached from a previous call. * <li>NC domain ARCHIVING (ArchivingChannel) is mapped to ArchiveNotifyEventChannelFactory. * <li>NC domain LOGGING (LoggingChannel) is mapped to LoggingNotifyEventChannelFactory. * <li>If the CDB contains MACI/Channels/NotificationServiceMapping data, we try to find * a matching notify service first based on NC name, then on NC domain, then default. * <li>If no factory has been found, we use NotifyEventChannelFactory. * <li>Otherwise if the factory name found does not end in NotifyEventChannelFactory, * we append NotifyEventChannelFactory. * </ul> * @return notification channel factory name. */ public String getNotificationFactoryNameForChannel() { // try local cache if (ncFactoryName != null) { return ncFactoryName; } // factory name, with or without the "NotifyEventChannelFactory" suffix String ncFactoryNameTmp = ""; // We use hard-coded mappings for logging and archiving system NCs. // As described in ICT-494, these mappings can at the moment not be overridden by CDB mappings. // If we want to allow that, we'd have to also read the CDB mappings from the acsstartup scripts // that create these system NCs, or alternatively have these NCs created on demand instead of during // notify service startup. Then we could move the following code after the CDB access, // to be executed only as a fallback if no CDB mapping was found for the system NCs. if (domainName != null) { if (domainName.equals(ACS_NC_DOMAIN_ARCHIVING.value)) { ncFactoryNameTmp = ARCHIVE_NOTIFICATION_FACTORY_NAME.value; } else if (domainName.equals(ACS_NC_DOMAIN_LOGGING.value)) { ncFactoryNameTmp = LOGGING_NOTIFICATION_FACTORY_NAME.value; } } // lazy initialization of CDB access DAOProxy channelsDAO = null; if (ncFactoryNameTmp.isEmpty()) { try { CDBAccess cdbAccess = new CDBAccess(m_services.getAdvancedContainerServices().getORB(), m_logger); cdbAccess.setDAL(m_services.getCDB()); channelsDAO = cdbAccess.createDAO("MACI/Channels"); } catch (Throwable th) { // keep track of when this occurs Long timeLastError = channelConfigProblemTimestamp; channelConfigProblemTimestamp = System.currentTimeMillis(); // don't log this too often (currently only once) if (timeLastError == 0l) { m_logger.log(AcsLogLevel.CONFIG, "Config issue for channel '" + channelName + "'. " + "Failed to get MACI/Channels DAO from CDB. Will use default notification service."); } } } // query CDB... if (channelsDAO != null) { // if channel mapping exists take it, wildchars are also supported try { // Note that in spite of the NC domain name being appended in the name service mappings, // we still configure simple NC names in the CDB. String[] channelNameList = channelsDAO.get_string_seq("NotificationServiceMapping/Channels_"); for (String pattern : channelNameList) { String regExpStr = WildcharMatcher.simpleWildcardToRegex(pattern); if (Pattern.matches(regExpStr, channelName)) { ncFactoryNameTmp = channelsDAO.get_string("NotificationServiceMapping/Channels_/" + pattern + "/NotificationService"); break; } } } catch (Throwable th) { m_logger.finer("No Channel to NotificationService mapping found for channel: " + channelName); } // try domain mapping, if given if (ncFactoryNameTmp.isEmpty() && domainName != null) { try { ncFactoryNameTmp = channelsDAO.get_string("NotificationServiceMapping/Domains/" + domainName + "/NotificationService"); if (m_logger.isLoggable(Level.FINEST)) { m_logger.finest("NC '" + channelName + "' of domain '" + domainName + "' mapped to NotificationService '" + ncFactoryNameTmp + "'."); } } catch (Throwable th) { if (m_logger.isLoggable(Level.FINER)) { m_logger.finer("No Domain to NotificationService mapping found for domain/channel: " + domainName + "/" + channelName); } } } // use default from CDB if (ncFactoryNameTmp.isEmpty()) { try { ncFactoryNameTmp = channelsDAO.get_string("NotificationServiceMapping/DefaultNotificationService"); } catch (Throwable th) { m_logger.finer("No NotificationServiceMapping/DefaultNotificationService attribute found, returning hardcoded default."); } } } if (!ncFactoryNameTmp.endsWith(alma.acscommon.NOTIFICATION_FACTORY_NAME.value)) { // If we found nothing in the CDB, we default to "NotifyEventChannelFactory". // Or if the CDB data did not contain the magical service name suffix, we add it here (see http://jira.alma.cl/browse/COMP-9260) ncFactoryNameTmp += alma.acscommon.NOTIFICATION_FACTORY_NAME.value; } ncFactoryName = ncFactoryNameTmp; return ncFactoryName; } /** * The following returns a map where each key is the name of an event and the * value is the maximum amount of time (in floating point seconds) an event receiver has * to process the event before a warning message is logged. * * @param channelName name of the channel * @return HashMap described above */ public HashMap<String, Double> getEventHandlerTimeoutMap() { // initialize the return value HashMap<String, Double> retVal = new HashMap<String, Double>(); // data access object to traverse the ACS CDB DAO dao = null; // keys into the DAO String[] keys = null; // get the dao for the channel... // ...if this fails, just return. try { dao = m_services.getCDB().get_DAO_Servant( "MACI/Channels/" + channelName); } catch (Exception e) { m_logger.finer("No CDB entry found for '" + channelName + "' channel"); return retVal; } // names of all the events try { keys = dao.get_string_seq("Events"); } catch (Exception e) { m_logger.finer("CDB entry found for '" + channelName + "' but no Events element."); return retVal; } // sanity check on the number of events if (keys.length == 0) { m_logger.finer("No event definitions found for the '" + channelName + "' within the CDB."); return retVal; } // populate the hashmap for (int i = 0; i < keys.length; i++) { // determine the value location String timeoutLocation = "Events/" + keys[i] + "/MaxProcessTime"; // get the value (floating point seconds) try { double value = dao.get_double(timeoutLocation); retVal.put(keys[i], new Double(value)); } catch (Exception e) { e.printStackTrace(); m_logger .severe("Could not convert 'MaxProcessTime' to floating " + "point seconds for the '" + channelName + "' channel and '" + keys[i] + "' event type."); } } return retVal; } public EventChannelFactory getNotifyFactory() { return notifyFactory; } /** * Corba spec: If the implementation of the target object is not capable of supporting * any of the requested administrative property settings, the UnsupportedAdmin exception is raised. * This exception has associated with it a list of name-value pairs of which each name * identifies an administrative property whose requested setting could not be satisfied, * and each associated value the closest setting for that property that could be satisfied. * @param ex * @return a String that contains the information from UnsupportedAdmin */ public String createUnsupportedAdminLogMessage(UnsupportedAdmin ex) { StringBuilder sb = new StringBuilder(); sb.append("Caught " + ex.getMessage() + ": The administrative properties specified for the '" + channelName + "' channel are not supported: "); if (ex.admin_err != null) { for (PropertyError propertyError : ex.admin_err) { sb.append("code=" + propertyError.code).append("; "); sb.append("name=" + propertyError.name); // TODO: Figure out what type (inside the Any) the range values are and add something like the following // sb.append("available_low=" + propertyError.available_range.low_val.extract_long()); // or use alma.acs.nc.AnyAide.#corbaAnyToObject(Any) } } return sb.toString(); } /** * Appends a random number to the given client name * and replaces '/' in the name with '_'. * <p> * This is used when setting names on NC proxy objects via the TAO extension API. * It reduces the risk of creating a new object with an existing name, * because TAO has a memory bug and will not delete the badly named object * even if it throws the correct NameAlreadyUsed exception. * Replacing of slashes is done so that clients of the TAO MC extension API * can suppress uninteresting parts of the pathname without mutilating the client name itself, * see http://ictjira.alma.cl/browse/ICT-3551. * <p> * This method is synchronized just to overcome residual doubts about the thread safety * of random#nextInt. * * @param clientName * @return "clientName-randomNumber" * @see #random */ public static synchronized String createRandomizedClientName(String clientName) { StringBuffer clientNameSB = new StringBuffer(); clientNameSB.append(clientName.replace("/", "_")); clientNameSB.append('-'); clientNameSB.append(String.format("%05d", random.nextInt(Integer.MAX_VALUE))); return clientNameSB.toString(); } }