/* * ALMA - Atacama Large Millimiter Array * (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 */ package alma.acs.container; import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import java.util.Iterator; import org.omg.PortableServer.Servant; import com.cosylab.CDB.DAL; import com.cosylab.CDB.DALHelper; import si.ijs.maci.AuthenticationData; import si.ijs.maci.CBComponentInfo; import si.ijs.maci.ClientOperations; import si.ijs.maci.ClientType; import si.ijs.maci.ComponentInfo; import si.ijs.maci.Container; import si.ijs.maci.ContainerHelper; import si.ijs.maci.ContainerOperations; import si.ijs.maci.ContainerPOA; import si.ijs.maci.ImplLangType; import alma.Logging.LoggingConfigurablePackage.LogLevels; import alma.Logging.ACSLogStatisticsPackage.LogStatsInformation; import alma.ACS.ACSComponentOperations; import alma.ACS.CBDescIn; import alma.ACS.CBDescOut; import alma.ACS.ComponentStates; import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx; import alma.ACSErrTypeCommon.wrappers.AcsJIllegalArgumentEx; import alma.ACSErrTypeCommon.wrappers.AcsJUnknownEx; import alma.AcsContainerLog.LOG_CompAct_Corba_OK; import alma.AcsContainerLog.LOG_CompAct_Init_OK; import alma.AcsContainerLog.LOG_CompAct_Instance_OK; import alma.AcsContainerLog.LOG_CompAct_Loading_OK; import alma.JavaContainerError.wrappers.AcsJContainerEx; import alma.JavaContainerError.wrappers.AcsJContainerServicesEx; import alma.acs.classloading.AcsComponentClassLoader; import alma.acs.component.ComponentDescriptor; import alma.acs.component.ComponentLifecycle; import alma.acs.container.corba.AcsCorba; import alma.acs.exceptions.AcsJException; import alma.acs.logging.AcsLogLevel; import alma.acs.logging.AcsLogger; import alma.acs.logging.ClientLogManager; import alma.acs.logging.config.LogConfig; import alma.acs.logging.config.LogConfig.LockableUnnamedLogger; import alma.acs.logging.config.LogConfigException; import alma.maci.loggingconfig.NamedLogger; import alma.acs.logging.level.AcsLogLevelDefinition; import alma.acs.util.IsoDateFormat; import alma.acs.util.StopWatch; import alma.acs.util.UTCUtility; import alma.alarmsystem.source.ACSAlarmSystemInterfaceFactory; import alma.maci.containerconfig.types.ContainerImplLangType; import alma.maci.loggingconfig.UnnamedLogger; import alma.maciErrType.CannotActivateComponentEx; import alma.maciErrType.CannotRestartComponentEx; import alma.maciErrType.ComponentDeactivationFailedEx; import alma.maciErrType.ComponentDeactivationUncleanEx; import alma.Logging.IllegalLogLevelsEx; import alma.Logging.LoggerDoesNotExistEx; import alma.maciErrType.wrappers.AcsJCannotActivateComponentEx; import alma.maciErrType.wrappers.AcsJCannotRestartComponentEx; import alma.maciErrType.wrappers.AcsJComponentDeactivationFailedEx; import alma.maciErrType.wrappers.AcsJComponentDeactivationUncleanEx; /** * The main container class that interfaces with the maci manager. * By extending <code>ContainerPOA</code>, an instance of this class is a corba object * that implements the maci container interface. * <p> * Only one instance of this class can be created per JVM. * <p> * @author hsommer * created 24-Sep-2002 14:24:48 */ public class AcsContainer extends ContainerPOA { /** * It's a singleton, but not with a static getAcsContainer() method to restrict access; * <code>s_instance</code> is needed to enforce single instantiation. */ private static volatile AcsContainer s_instance; /** * Start time, used during login to the manager. */ private final long startTimeUTClong; private final String m_containerName; /** * An ID assigned by the manager at first login. * This ID survives logout/login to the manager. * For container shutdown/crashes, a new ID is assigned the following login. */ private long executionId = -1; private final AcsManagerProxy m_managerProxy; private final AcsCorba m_acsCorba; private final boolean isEmbedded; private final AcsLogger m_logger; private final ThreadFactory containerThreadFactory; final ThreadPoolExecutor threadPoolExecutor; private final ComponentMap m_activeComponentMap; private ContainerServicesImpl m_alarmContainerServices; /** * Gate that blocks some (currently: only activate_component and LoggingConfigurable methods) * incoming calls while the container is initializing itself and therefore not fully ready. * In particular, this will be used to hold component activation requests for autostart components, * which the manager sends right after the container has logged in to the manager. */ private final CountDownLatch containerStartOrbThreadGate; /** * Singleton logging config object shared with ClientLogManager. */ private LogConfig logConfig; /** * This reference to the CDB is shared among container, components, alarm libs etc. * No data cache is shared, and each of these CDB clients must register for updates as needed. */ private DAL sharedCdbRef; private final AtomicBoolean shuttingDown = new AtomicBoolean(false); /** see comments in {@link #authenticate(String)} */ private boolean useRecoveryMode = true; /** * Overrides {@link #useRecoveryMode} if non-null. */ private Boolean recoveryModeOverride = null; /** See comments in {@link #disconnect()} */ private int m_managerRetry; /** actions for shutdown() */ public static final int CONTAINER_RELOAD = 0; public static final int CONTAINER_REBOOT = 1; public static final int CONTAINER_EXIT = 2; ///////////////////////////////////////////////////////////// // creation, initialization of container ///////////////////////////////////////////////////////////// /** * Constructor which creates a container that is registered as a CORBA object, but not yet logged in to the manager * (for that, call {@link #initialize()}. * * @param containerName * @param acsCorba * @param managerProxy * @param isEmbedded * true if this container runs within an application. Affects shutdown behavior. * @throws AcsJContainerEx * if anything goes wrong, or if another instance of this class has already been created. */ AcsContainer(String containerName, AcsCorba acsCorba, AcsManagerProxy managerProxy, boolean isEmbedded) throws AcsJContainerEx { if (s_instance == null) { startTimeUTClong = UTCUtility.utcJavaToOmg(System.currentTimeMillis()); m_containerName = containerName; containerStartOrbThreadGate = new CountDownLatch(1); m_managerProxy = managerProxy; this.isEmbedded = isEmbedded; m_logger = ClientLogManager.getAcsLogManager().getLoggerForContainer(containerName); containerThreadFactory = new CleaningDaemonThreadFactory(m_containerName, m_logger, "Container"); // @TODO: Use "jacorb.poa.thread_pool_max" for max number of activation threads, // but without direct coupling to jacorb-specific variable. threadPoolExecutor = new ThreadPoolExecutor(10, 10, 3, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(), containerThreadFactory, new ThreadPoolExecutor.AbortPolicy()); threadPoolExecutor.allowCoreThreadTimeOut(true); m_activeComponentMap = new ComponentMap(m_logger); m_acsCorba = acsCorba; // @TODO: Check with C++ and Py container how they use ContainerStatusORBInitBeginMsg. // Here we interpret is as registering the container object with the ORB, // but it could mean "initialization of the ORB itself", and then these logs should move to // AcsContainerRunner.run(String[]), around initCorba and runCorba System.out.println(ContainerOperations.ContainerStatusORBInitBeginMsg); registerWithCorba(); System.out.println(ContainerOperations.ContainerStatusORBInitEndMsg); s_instance = this; } else { AcsJContainerEx ex = new AcsJContainerEx(); ex.setContextInfo("illegal attempt to create more than one instance of " + AcsContainer.class.getName() + " inside one JVM."); throw ex; } } /** * Container initialization such as logging in to the manager, configuring logging, initializing the alarm system. * This is taken out of the ctor just to keep is lean and be able to instantiate a minimum container for testing. * @throws AcsJContainerServicesEx for any serious issue that should make container start fail. */ void initialize() throws AcsJContainerEx { // CDB ref try { org.omg.CORBA.Object dalObj = m_managerProxy.get_service("CDB", false); sharedCdbRef = DALHelper.narrow(dalObj); } catch (Exception ex) { m_logger.log(Level.SEVERE, "Failed to access the CDB.", ex); throw new AcsJContainerEx(ex); } if (sharedCdbRef == null) { m_logger.log(Level.SEVERE, "Failed to access the CDB."); throw new AcsJContainerEx(); } // CDB container config (must be done before manager login) initFromCdb(true); // TODO: sharedCdbRef.add_change_listener / listen_for_changes and call initFromCdb(false) on change. // manager login System.out.println(ContainerOperations.ContainerStatusMgrInitBeginMsg); // TODO: Use CDB attributes ManagerRetry and Recovery for manager login. loginToManager(m_managerRetry); System.out.println(ContainerOperations.ContainerStatusMgrInitEndMsg); // init logging logConfig = ClientLogManager.getAcsLogManager().getLogConfig(); // logConfig.setInternalLogger(m_logger); logConfig.setCDBLoggingConfigPath("MACI/Containers/" + m_containerName); logConfig.setCDB(sharedCdbRef); try { logConfig.initialize(false); } catch (LogConfigException ex) { // if the CDB can't be read, we still want to run the container, thus we only log the problem here m_logger.log(Level.FINE, "Failed to configure logging (default values will be used).", ex); } // init the alarm system // // TODO: clean up the construction of CS which is ad-hoc implemented right before ACS 7.0 // in order to allow CERN alarm libs to get their static field for ContainerServices set. // Currently the alarm system acts under the container name. // Setting of static fields in the AS classes should be removed altogether. // TODO: maybe we should pass a alarms@container logger instead of the normal one m_alarmContainerServices = new ContainerServicesImpl(m_managerProxy, sharedCdbRef, m_acsCorba.createPOAForComponent("alarmSystem"), m_acsCorba, m_logger, 0, m_containerName, null, containerThreadFactory) { private AcsLogger alarmLogger; public AcsLogger getLogger() { if (alarmLogger == null) { // @TODO perhaps get a container logger "alarms@containername" alarmLogger = ClientLogManager.getAcsLogManager().getLoggerForContainer(getName()); } return alarmLogger; } }; try { ACSAlarmSystemInterfaceFactory.init(m_alarmContainerServices); } catch (Throwable thr) { AcsJContainerEx ex = new AcsJContainerEx(thr); ex.setContextInfo("Error initializing the alarm system factory"); throw ex; } // enable (throttle) alarms from the logging subsystem ClientLogManager.LogAlarmHandler logAlarmHandler = new ClientLogManager.LogAlarmHandler() { @Override public void raiseAlarm(String faultFamily, String faultMember, int faultCode) throws AcsJCouldntPerformActionEx { m_alarmContainerServices.getAlarmSource().raiseAlarm(faultFamily, faultMember, faultCode); } @Override public void clearAlarm(String faultFamily, String faultMember, int faultCode) throws AcsJCouldntPerformActionEx { m_alarmContainerServices.getAlarmSource().clearAlarm(faultFamily, faultMember, faultCode); } }; ClientLogManager.getAcsLogManager().enableLoggingAlarms(logAlarmHandler); // init the BACI framework try { Class<?> clazz = Class.forName("alma.ACS.jbaci.BACIFramework"); Object baciFramework = clazz.getField("INSTANCE").get(null); clazz.getMethod("initialize", ThreadFactory.class).invoke(baciFramework, containerThreadFactory); } catch(Exception e) { AcsJContainerEx ex = new AcsJContainerEx(e); ex.setContextInfo("Error initializing the BACI framework"); // TODO: This is temporary, just to avoid test crashes. The exception should be actually thrown m_logger.log(AcsLogLevel.WARNING, "Error initializing the BACI framework, container will run withouth the BACI Framework initialized", ex); //throw ex; } // unleash any waiting ORB threads that were held until container init has finished containerStartOrbThreadGate.countDown(); } /** * Retrieves the container configuration from the CDB and uses it to initialize the container. * If no such configuration can be obtained from the CDB, then XSD default values are used. * <ul> * <li><code>Timeout</code> is applied for ORB-level client-side timeouts. * <li><code>UseIFR</code>: The container never uses the IFR. The only indirect support is that method * {@link AcsCorba#createOrb(String[] args, Integer port)} sets the matching "-ORBInitRef" arg before ORB initialization, * to support "_get_interface()" calls by components such as the sampling system. * TODO: Should we suppress setting this initial service reference if useIFR==false? * According to Corba spec 4.5.1 we may call ORB#init again and possibly override the init ref setting that was given * when initializing the ORB in the first place (before the CDB could be accessed). * <li><code>Recovery</code>: Will be used during container login with the manager. * <li><code>ManagerRetry</code>: TODO: use it * <li><code>ServerThreads</code>: TODO: Can we override the orb.properties setting from within the application? * <li><code>LoggingConfig</code> Is taken care of separately by the acsjlog code (class LogConfig) * <li>Other CDB attributes either don't apply to a java container or are meant to be read by the OMC or the manager * instead of by the container: Autoload, DALtype, PingInterval, DeployInfo.{Flags/Host/KeepAliveTime/StartOnDemand/TypeModifiers}. * </ul> * @param isContainerStart Allows different treatment (for some container options) * between the first invocation during the container start and later invocations during CDB refreshs. */ protected void initFromCdb(boolean isContainerStart) { alma.maci.containerconfig.Container containerConfig = null; boolean isDefaultConfig = false; // read config data String cdbPath = "MACI/Containers/" + m_containerName; try { String xml = sharedCdbRef.get_DAO(cdbPath); containerConfig = alma.maci.containerconfig.Container.unmarshalContainer(new StringReader(xml)); } catch (Exception ex) { // use xsd defaults containerConfig = new alma.maci.containerconfig.Container(); m_logger.warning("Failed to access container configuration data in the CDB. Will use XSD defaults."); isDefaultConfig = true; } // Apply corba client-side timeout double timeoutSeconds = containerConfig.getTimeout(); try { m_acsCorba.setORBLevelRoundtripTimeout(timeoutSeconds); } catch (Exception ex) { m_logger.log(Level.WARNING, "No CDB timeout applied to the container.", ex); } // ImplLang: only validate if (!isDefaultConfig) { // Check for possible CDB misconfiguration if (containerConfig.getImplLang() != ContainerImplLangType.JAVA) { m_logger.warning("Bad CDB configuration for '" + cdbPath + "'. Should be ImplLang==java."); } } // Recovery if (recoveryModeOverride != null) { useRecoveryMode = recoveryModeOverride.booleanValue(); m_logger.fine("Component recovery = '" + useRecoveryMode + "' (from local setting overriding CDB/XSD)"); } else { useRecoveryMode = containerConfig.getRecovery(); m_logger.fine("Component recovery = '" + useRecoveryMode + "' (from CDB/XSD)"); } m_managerRetry=containerConfig.getManagerRetry(); } /** * Allows overriding the recovery mode, e.g. using command line options that override the CDB setting. * @param recoveryModeOverride May be <code>null</code> in which case it will not override anything. */ void setRecoveryModeOverride(Boolean recoveryModeOverride) { this.recoveryModeOverride = recoveryModeOverride; } /** * To be called only once from the ctor. * * @throws AcsJContainerEx */ private void registerWithCorba() throws AcsJContainerEx { // activate the Container as a CORBA object. org.omg.CORBA.Object obj = m_acsCorba.activateContainer(this, m_containerName); if (obj == null) { AcsJContainerEx ex = new AcsJContainerEx(); ex.setContextInfo("failed to register this AcsContainer with the ORB."); throw ex; } Container container; try { container = ContainerHelper.narrow(obj); if (container == null) { throw new NullPointerException("Container CORBA-narrow returned a null."); } } catch (Throwable thr) { AcsJContainerEx ex = new AcsJContainerEx(); ex.setContextInfo("failed to narrow the AcsContainer to CORBA type Container."); throw ex; } m_logger.finer("AcsContainer successfully registered with the ORB as a Container"); } /** * Will attempt to log into the manager. * If the manager reference is not available, will enter a loop and keep trying. * * @param attempts The number of attempts to find and login into the manager (0 means forever) * * @throws AcsJContainerServicesEx If login fails on an available manager */ protected void loginToManager(int attempts) throws AcsJContainerEx { m_managerProxy.loginToManager(m_acsCorba.getContainerCorbaRef(this), attempts); } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#get_handle ///////////////////////////////////////////////////////////// public int get_handle() { return m_managerProxy.getManagerHandle(); } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#activate_component ///////////////////////////////////////////////////////////// /** * Activates a component so that it's ready to receive functional calls * after returning from this method. Called by the ACS Manager. * <p> * From MACI IDL: * <i> * Activate a component whose type (class) and name (instance) are given. * In the process of activation, component's code-base is loaded into memory if it is not there already. * The code-base resides in an executable file (usually a dynamic-link library or a shared library -- DLL). * On platforms that do not automatically load dependent executables (e.g., VxWorks), * the container identifies the dependencies by querying the executable and loads them automatically. * Once the code is loaded, it is asked to construct a servant of a given type. * The servant is then initialized with the Configuration Database (CDB) and Persistance Database (PDB) data. * The servant is attached to the component, and a reference to it is returned. * </i> * <p> * @param componentHandle handle of the component that is being activated. This handle is used * by the component when it will present itself to the Manager. * The component is expected to remember this handle for its entire life-time. * @param execution_id * @param compName name of the component to instantiate (instance name, comes from CDB) * @param exe component helper implementation class; must be a subclass of * {@link alma.acs.container.ComponentHelper}. * @param type the type of the component to instantiate (Corba IR id). * @return Returns the reference to the object that has just been activated. * If the component could not the activated, a nil reference is returned. * * @see si.ijs.maci.ContainerOperations#activate_component(int, String, String, String) */ public ComponentInfo activate_component(int componentHandle, long execution_id, String compName, String exe, String type) throws CannotActivateComponentEx { // reject the call if container is shutting down if (shuttingDown.get()) { String msg = "activate_component() rejected because of container shutdown."; m_logger.fine(msg); AcsJCannotActivateComponentEx ex = new AcsJCannotActivateComponentEx(); ex.setCURL(compName); ex.setDetailedReason(msg); throw ex.toCannotActivateComponentEx(); } ComponentInfo componentInfo = null; StopWatch activationWatch = new StopWatch(m_logger); // to make component activations stick out in the log list m_logger.finer("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); m_logger.fine("activate_component: handle=" + componentHandle + " name=" + compName + " helperClass=" + exe + " type=" + type); // if the container is still starting up, then hold the request until the container is ready boolean contInitWaitSuccess = false; try { contInitWaitSuccess = containerStartOrbThreadGate.await(30, TimeUnit.SECONDS); } catch (InterruptedException ex1) { // just leave contInitWaitSuccess == false } if (!contInitWaitSuccess) { String msg = "Activation of component " + compName + " timed out after 30 s waiting for the container to finish its initialization."; m_logger.warning(msg); AcsJCannotActivateComponentEx ex = new AcsJCannotActivateComponentEx(); ex.setCURL(compName); ex.setDetailedReason(msg); throw ex.toCannotActivateComponentEx(); } ComponentAdapter compAdapter = null; try { synchronized (m_activeComponentMap) { ComponentAdapter existingCompAdapter = getExistingComponent(componentHandle, compName, type); if (existingCompAdapter != null) { return existingCompAdapter.getComponentInfo(); } else if (!m_activeComponentMap.reserveComponent(componentHandle)) { AcsJContainerEx ex = new AcsJContainerEx(); ex.setContextInfo("Component with handle '" + componentHandle + "' is already being activated by this container. Manager should have prevented double activation."); throw ex; } } ClassLoader compCL = null; // the property 'acs.components.classpath.jardirs' is set by the script acsStartContainer // to a list of all relevant 'lib/ACScomponents/' directories String compJarDirs = System.getProperty(AcsComponentClassLoader.PROPERTY_JARDIRS); if (compJarDirs != null) { compCL = new AcsComponentClassLoader(Thread.currentThread().getContextClassLoader(), m_logger, compName); } else { // fallback: load component impl classes in the global class loader compCL = Thread.currentThread().getContextClassLoader(); } // Create component helper using component classloader. // Note that the base class alma.acs.container.ComponentHelper will still be loaded by the container CL, // although the current subclassing design is a bit dirtier than it could be in the sense that a mean // component could deploy modified container classes (e.g. in method getInterfaceTranslator). // Nothing big to worry about though... ComponentHelper compHelper = createComponentHelper(compName, exe, compCL); // Creates component implementation and connects it with the Corba-generated POATie object. // Objects for container interception ("tight container") and for automatic xml binding class // de-/serialization are chained up and inserted here. End-to-end they have to translate between the // operations interface derived from corba IDL and the component's declared internalInterface. StopWatch compStopWatch = new StopWatch(); ComponentLifecycle compImpl = compHelper.getComponentImpl(); LOG_CompAct_Instance_OK.log(m_logger, compName, compStopWatch.getLapTimeMillis()); //m_logger.finest(compName + " component impl created, with classloader " + compImpl.getClass().getClassLoader().getClass().getName()); Class<? extends ACSComponentOperations> operationsIFClass = compHelper.getOperationsInterface(); Constructor<? extends Servant> poaTieCtor = compHelper.getPOATieClass().getConstructor(new Class[]{operationsIFClass}); Object operationsIFImpl = null; // todo: use different condition (e.g. a new boolean from compHelper) so that a component // can implement both the operations IF and the inner IF, e.g. to do manual parameter // translations for some methods only... if (operationsIFClass.isInstance(compImpl)) { m_logger.finer("component " + compName + " implements operations interface directly; no dynamic translator proxy used."); operationsIFImpl = compImpl; } else { m_logger.finer("creating dynamic proxy to map corba interface calls to component " + compName + "."); operationsIFImpl = compHelper.getInterfaceTranslator(); if( !Proxy.isProxyClass(operationsIFImpl.getClass()) && !(operationsIFImpl instanceof ExternalInterfaceTranslator) ) m_logger.log(AcsLogLevel.NOTICE,"interface translator proxy for component " + compName + " isn't " + "the default one, and doesn't expose the default as one either. This may cause problem when invoking " + "xml-aware offshoot getters"); } // make it a tight container (one that intercepts functional method calls) String[] methodsExcludedFromInvocationLogging = compHelper.getComponentMethodsExcludedFromInvocationLogging(); Object poaDelegate = ContainerSealant.createContainerSealant( operationsIFClass, operationsIFImpl, compName, false, m_logger, compCL, methodsExcludedFromInvocationLogging); // construct the POATie skeleton with operationsIFImpl as the delegate object Servant servant = null; try { servant = poaTieCtor.newInstance(new Object[]{poaDelegate}); } catch (Throwable thr) { AcsJContainerEx ex = new AcsJContainerEx(thr); ex.setContextInfo("failed to instantiate the servant object for component " + compName + " of type " + compImpl.getClass().getName()); throw ex; } // // administrate the new component // compAdapter = new ComponentAdapter(compName, type, exe, componentHandle, m_containerName, compImpl, m_managerProxy, sharedCdbRef, compCL, m_logger, m_acsCorba); // to support automatic offshoot translation for xml-binded offshoots, we need to pass the dynamic adaptor if( !operationsIFClass.isInstance(compImpl) ) { // if an external interface translator was given by the user, get the default interface translator if( operationsIFImpl instanceof ExternalInterfaceTranslator ) operationsIFImpl = ((ExternalInterfaceTranslator)operationsIFImpl).getDefaultInterfaceTranslator(); compAdapter.setComponentXmlTranslatorProxy(operationsIFImpl); } // for future offshoots created by this component we must pass on the no-auto-logging info compAdapter.setMethodsExcludedFromInvocationLogging(methodsExcludedFromInvocationLogging); compStopWatch.reset(); compAdapter.activateComponent(servant); LOG_CompAct_Corba_OK.log(m_logger, compName, compStopWatch.getLapTimeMillis()); // now it's time to turn off ORB logging if the new component is requesting this if (compHelper.requiresOrbCentralLogSuppression()) { ClientLogManager.getAcsLogManager().suppressCorbaRemoteLogging(); } // even though the component is now an activated Corba object already, // it won't be called yet since the maciManager will only pass around // access information after we've returned from this activate_component method. // Therefore it's not too late to call initialize and execute, which are // guaranteed to be called before incoming functional calls must be expected. // At the moment we have to call these two methods one after the other; // if the Manager supports new calling semantics, we could separate the two // as described in ComponentLifecycle m_logger.fine("about to initialize component " + compName); compStopWatch.reset(); compAdapter.initializeComponent(); compAdapter.executeComponent(); LOG_CompAct_Init_OK.log(m_logger, compName, compStopWatch.getLapTimeMillis()); // we've deferred storing the component in the map until after it's been initialized successfully m_activeComponentMap.put(componentHandle, compAdapter); long activTime = activationWatch.getLapTimeMillis(); m_logger.info("component " + compName + " activated and initialized in " + activTime + " ms."); componentInfo = compAdapter.getComponentInfo(); } catch (Throwable thr) { m_logger.log(Level.SEVERE, "Failed to activate component " + compName + ", problem was: ", thr); if (compAdapter != null) { try { compAdapter.deactivateComponent(); } catch (Exception ex) { m_logger.log(Level.FINE, ex.getMessage(), ex); } } m_activeComponentMap.remove(componentHandle); AcsJCannotActivateComponentEx ex = new AcsJCannotActivateComponentEx(thr); throw ex.toCannotActivateComponentEx(); } finally { // to make (possibly nested) component activations stick out in the log list m_logger.finer(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); } return componentInfo; } /* (non-Javadoc) * @see si.ijs.maci.ContainerOperations#activate_component_async(int, long, java.lang.String, java.lang.String, java.lang.String, si.ijs.maci.CBComponentInfo, alma.ACS.CBDescIn) */ public void activate_component_async(final int h, final long execution_id, final String name, final String exe, final String type, final CBComponentInfo callback, final CBDescIn desc) { m_logger.finer("activate_component_async request received for '" + name + "', enqueueing (taskCount: " + threadPoolExecutor.getTaskCount() + ", active threads: " + threadPoolExecutor.getActiveCount() + ", maxPoolSize: " + threadPoolExecutor.getMaximumPoolSize() + ")."); threadPoolExecutor.execute(new Runnable() { @Override public void run() { m_logger.finer("activate_component_async request for '" + name + "' is being processed now."); CBDescOut descOut = new CBDescOut(0, desc.id_tag); ComponentInfo componentInfo=null; try { componentInfo = activate_component(h, execution_id, name, exe, type); } catch (CannotActivateComponentEx ae) { AcsJCannotActivateComponentEx aae = AcsJCannotActivateComponentEx.fromCannotActivateComponentEx(ae); ComponentInfo dummyComponentInfo = new ComponentInfo( type, exe, null, name, new int[0], 0, m_containerName, h, 0, new String[0] ); callback.done(dummyComponentInfo, aae.toAcsJCompletion().toCorbaCompletion(), descOut); } catch (Throwable th) { AcsJException ae = new AcsJUnknownEx(th); ComponentInfo dummyComponentInfo = new ComponentInfo( type, exe, null, name, new int[0], 0, m_containerName, h, 0, new String[0] ); callback.done(dummyComponentInfo, ae.toAcsJCompletion().toCorbaCompletion(), descOut); } // Try to invoke the callback several times before giving up int retry=0; boolean notified=false; while (retry<3 && !notified) { try { m_logger.log(AcsLogLevel.DELOUSE, "Calling maci::CBComponentInfo::done with descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"'"); callback.done(componentInfo, new alma.ACSErrTypeOK.wrappers.ACSErrOKAcsJCompletion().toCorbaCompletion(), descOut); notified=true; m_logger.log(AcsLogLevel.DELOUSE, "Call to maci::CBComponentInfo::done with descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"' completed"); } catch (Throwable t) { retry++; m_logger.log(AcsLogLevel.DELOUSE, "Call to maci::CBComponentInfo::done with descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"' failed, retrying...",t); try { Thread.sleep(1000); } catch (InterruptedException ie) {} } } if (!notified) { m_logger.log(AcsLogLevel.ERROR, "Call to maci::CBComponentInfo::done with descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"' failed, deactivating the component."); try { deactivate_component(h); } catch (ComponentDeactivationUncleanEx e) { m_logger.log(AcsLogLevel.WARNING, "UNclean deactivation of component descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"'",e); } catch (ComponentDeactivationFailedEx e) { m_logger.log(AcsLogLevel.WARNING, "Deactivation of component failed descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"'",e); } catch (Throwable t) { m_logger.log(AcsLogLevel.WARNING, "Deactivation of component failed descOut.id_tag = %d."+descOut.id_tag+" for '"+name+"'",t); } } } }); } /** * Checks if there is an existing component that matches the spec. * This can happen if the Manager went down and back up. * Strategy: * <ul> * <li>Same handles: * <ul> * <li>reuses component if name and type match; * <li>deactivates existing component if name or type don't match; * then returns <code>null</code>. * <li> * </ul> * <li>Different handles: * <ul> * <li>reuses component if name and type match, with the existing handle * (the manager will have to update its handle table) * <li>just returns null if name or type don't match; * this is the standard case of activating a genuinly new component. * </ul> * </ul> * @param componentHandle * @param name * @param type * @return the adapter for an existing component according to the above rules, or <code>null</code> if none exists. */ private ComponentAdapter getExistingComponent(int componentHandle, String name, String type) { // try same handle ComponentAdapter existingCompAdapter = m_activeComponentMap.get(componentHandle); if (existingCompAdapter != null) { String msg = "component with handle " + componentHandle + " ('" + name + "') already activated"; m_logger.warning(msg); // compare name and type to distinguish real identity and handle fraud if (existingCompAdapter.getName().equals(name) && existingCompAdapter.getType().equals(type) ) { String msgReuse = "will reuse existing component " + name; m_logger.warning(msgReuse); } else { String msgMismatch = "component with handle " + componentHandle + ": name and/or type don't match between existing and requested component: " + "existing(" + existingCompAdapter.getName() + ", " + existingCompAdapter.getType() + ") vs. " + "requested(" + name + ", " + type + "). " + "Will deactivate existing component and load the requested."; m_logger.warning(msgMismatch); try { deactivate_component(componentHandle); } catch (Exception ex) { m_logger.log(Level.FINE, "Failed to deactivate existing component with handle " + componentHandle); } existingCompAdapter = null; } } else // different handles { // check for other comp with same name (but different handle). // we've had this illegal case happen before, // resulting in an exception when attempting to create // the new POA for the new component with the same name. // Even though this should never happen, we take account of reality (maciManager-bug?) // and reuse a component with the same name if we find one. existingCompAdapter = m_activeComponentMap.getComponentByNameAndType(name, type); if (existingCompAdapter != null) { int orgHandle = existingCompAdapter.getHandle(); String msg = "container refuses to activate component '" + name + "' (" + type + ") " + "since another component with the same name and type " + "has already been activated using a different handle (" + orgHandle + "). " + "Will keep the original handle."; m_logger.warning(msg); } else { // the most common case: all is different, we just leave existingCompAdapter == null } } return existingCompAdapter; } private ComponentHelper createComponentHelper(String compName, String exe, ClassLoader compCL) throws AcsJContainerEx { m_logger.finer("creating component helper instance of type '" + exe + "' using classloader " + compCL.getClass().getName()); StopWatch sw = new StopWatch(); Class<? extends ComponentHelper> compHelperClass = null; try { compHelperClass = (Class.forName(exe, true, compCL).asSubclass(ComponentHelper.class)); } catch (ClassNotFoundException ex) { AcsJContainerEx ex2 = new AcsJContainerEx(ex); ex2.setContextInfo("component helper class '" + exe + "' not found."); throw ex2; } catch (ClassCastException ex) { AcsJContainerEx ex2 = new AcsJContainerEx(); ex2.setContextInfo("component helper class '" + exe + "' does not inherit from required base class " + ComponentHelper.class.getName()); throw ex2; } // We really only measure the time to load the component helper class, // but since we expect the comp impl class to be in the same jar file, our class loader should // then learn about it and be very fast loading it later. LOG_CompAct_Loading_OK.log(m_logger, compName, sw.getLapTimeMillis()); Constructor<? extends ComponentHelper> helperCtor = null; ComponentHelper compHelper = null; try { helperCtor = compHelperClass.getConstructor(new Class[]{Logger.class}); } catch (NoSuchMethodException ex) { String msg = "component helper class '" + exe + "' has no constructor " + " that takes a java.util.Logger"; m_logger.fine(msg); AcsJContainerEx ex2 = new AcsJContainerEx(ex); ex2.setContextInfo(msg); throw ex2; } try { compHelper = helperCtor.newInstance(new Object[]{m_logger}); } catch (Throwable thr) { AcsJContainerEx ex = new AcsJContainerEx(thr); ex.setContextInfo("component helper class '" + exe + "' could not be instantiated"); throw ex; } // here we don't log LOG_CompAct_Instance_OK because instantiating the component itself is expected to take longer // than instantiating the comp helper here. // To be more accurate, we'd have to add up those times, which would be rather ugly in the current code. compHelper.setComponentInstanceName(compName); return compHelper; } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#set_component_shutdown_order ///////////////////////////////////////////////////////////// /** * Called by the manager to update the container's knowledge about optimum shutdown order * of its components. * This information will only be needed for a locally initiated container shutdown, * since the manager will call {@link #deactivate_component(int)} in a regular shutdown. */ public void set_component_shutdown_order(int[] handleSeq) { String handleSeqString = null; if (handleSeq == null) { handleSeqString = "null"; } else { handleSeqString = ""; for (int i = 0; i < handleSeq.length; i++) { handleSeqString += handleSeq[i] + " "; } m_activeComponentMap.sort(handleSeq); } m_logger.fine("set_component_shutdown_order called. Handles: " + handleSeqString); } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#deactivate_component ///////////////////////////////////////////////////////////// /** * Deactivates all components whose handles are given. * <p> * From maci.idl: <i>Deactivation is the inverse process of activation: component is detached from the POA, and thus * made unavailable through CORBA, and its resources are freed. If its code-base is no longer used, it is unloaded * from memory.</i> * * @param handles * a handle identifying the component that should be released. If <code>handle == 0</code>, then all * active components will be deactivated! * * @see si.ijs.maci.ContainerOperations#deactivate_component(int) * @see ComponentAdapter#deactivateComponent() * */ @Override public void deactivate_component(int handle) throws ComponentDeactivationUncleanEx, ComponentDeactivationFailedEx { m_logger.fine("received call to deactivate_component, handle=" + handle); // get the component adapter which will be != null, but might be in the wrong state ComponentAdapter[] compAdapters = m_activeComponentMap.getComponentAdapters(new int[] { handle }); // (synchronizedly) mark the component for deactivation first. // If an adapter is returned, it will be in the appropriate state; // another thread coming in with the same handle will not get this adapter back. ComponentAdapter[] validAdapters = markAndFilterForDeactivation(compAdapters); if (validAdapters.length > 1) { // @todo error } else if (validAdapters.length == 0) { // @todo log the no-op } else { ComponentAdapter compAdapter = validAdapters[0]; try { deactivateComponentInternal(compAdapter); } catch (AcsJComponentDeactivationUncleanEx ex) { throw ex.toComponentDeactivationUncleanEx(); } catch (AcsJComponentDeactivationFailedEx ex) { throw ex.toComponentDeactivationFailedEx(); } } } /** * Called internally during shutdown. * A deactivation exception will be logged at INFO level but will not prevent deactvation attempts of the other components. */ private void deactivateAllComponents() { // get the component adapters which are all != null, but might be in the wrong state ComponentAdapter[] compAdapters = m_activeComponentMap.getAllComponentAdapters(); // (synchronizedly) mark the component(s) for deactivation first. // resulting adapters are all in an appropriate state; // another thread coming in with overlapping handles will not get the same adapters back ComponentAdapter[] validAdapters = markAndFilterForDeactivation(compAdapters); // todo: perhaps use thread pool and deactivate a bunch in parallel for (ComponentAdapter compAdapter : validAdapters) { try { deactivateComponentInternal(compAdapter); } catch (Exception ex) { m_logger.log(Level.INFO, "failed to properly deactivate component " + compAdapter.getName(), ex); } } } /** * The common part for deactivating either one or many components. * This method will directly deactivate the given component adatper, without caring about its state * or synchronization issues. * <p> * Note that the container "forgets" about this component, even if deactivation fails with a * <code>AcsJComponentDeactivationFailedEx</code>: * <ul> * <li> If the poa deactivation failed with a timeout, it could be that it fully deactivates a bit later, * which would make the reference useless. * <li> Even if such a component stays around, it may be so messed up that it is too risky to use it further. * </ul> * The consequence is that an attempt to activate another instance of the same component may fail in the future, * without a specific message from the container that a previous instance had failed. The information can only * be connected from the logs in this case. * The exception parameter AcsJComponentDeactivationFailedEx#IsPermanentFailure therefore has no meaning for the manager. * @TODO complete this comment: A client might find it useful though, and set to false in case of timeout, because then the client can hope that this comp will disappear later. * * @throws AcsJComponentDeactivationFailedEx * @throws AcsJComponentDeactivationUncleanEx * @see #deactivate_component(int) * @see #deactivateAllComponents() */ private void deactivateComponentInternal(ComponentAdapter compAdapter) throws AcsJComponentDeactivationUncleanEx, AcsJComponentDeactivationFailedEx { try { compAdapter.deactivateComponent(); } finally { m_activeComponentMap.remove(compAdapter.getHandle()); } } /** * Filters out those components from <code>compAdapters</code> * which can be deactivated (not DESTROYING | ABORTING | DEFUNCT). * * Must be synchronized to avoid deactivating the same component more than once. * @param compAdapters * @return adapters of components that are ready to be deactivated */ private synchronized ComponentAdapter[] markAndFilterForDeactivation(ComponentAdapter[] compAdapters) { ArrayList<ComponentAdapter> adaptersForDestruction = new ArrayList<ComponentAdapter>(); for (int i = 0; i < compAdapters.length; i++) { try { ComponentAdapter compAdapter = compAdapters[i]; ComponentStates state = compAdapter.getComponentStateManager().getCurrentState(); if (state == ComponentStates.COMPSTATE_DESTROYING || state == ComponentStates.COMPSTATE_ABORTING || state == ComponentStates.COMPSTATE_DEFUNCT) { m_logger.fine("coming too late to deactivate component " + compAdapter.getName()); } else { compAdapter.getComponentStateManager().setStateByContainer(ComponentStates.COMPSTATE_DESTROYING); adaptersForDestruction.add(compAdapter); } } catch (Throwable thr) { m_logger.log(Level.WARNING, "something went wrong while marking components for destruction. Will continue.", thr); } } return ( adaptersForDestruction.toArray(new ComponentAdapter[adaptersForDestruction.size()]) ); } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#restart_component ///////////////////////////////////////////////////////////// /** * Not yet implemented. Left for ACS 3.1 (or later...), see comments at * http://almasw.hq.eso.org/almasw/bin/view/ACS/NewMaciIdl, e.g. * GianlucaChiozzi - 21 Oct 2003 * <i>We do not have really clear ideas yet about this. * I think that the most accepted ipothesis is that restart of a component means destroy/create * while restart of a container means shutdown/restart. * For ACS 3.0 I would forget about this issue and leave it for ACS 3.1.</i> * <p> * from maci idl: * <i>Restarts a component. Returns a new reference of the restarted component.</i> * * @param compHandle Handle of the component to be restarted. * @see si.ijs.maci.ContainerOperations#restart_component(int) */ public org.omg.CORBA.Object restart_component(int compHandle) throws CannotRestartComponentEx { String curl = null; try { ComponentAdapter compAdapter = m_activeComponentMap.get(compHandle); if (compAdapter != null) { curl = compAdapter.getName(); } m_logger.warning("method restart_component not yet implemented."); AcsJCannotRestartComponentEx ex = new AcsJCannotRestartComponentEx(); ex.setCURL(curl); throw ex; } catch (AcsJCannotRestartComponentEx ex) { throw ex.toCannotRestartComponentEx(); } catch (Throwable thr) { AcsJCannotRestartComponentEx ex = new AcsJCannotRestartComponentEx(thr); ex.setCURL(curl); throw ex.toCannotRestartComponentEx(); } } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#shutdown ///////////////////////////////////////////////////////////// /** * Action to take after shutting down (ignored for the time being). Bits 8 thru 15 of this parameter denote the * action, which can be one of: * <UL> * <LI>0 -- reload the container</LI> * <LI>1 -- reboot the computer</LI> * <LI>2 -- exit the container</LI> * </UL> * * The bits 0 thru 7 (values 0 to 255) are the return value that the Container should pass to the operating system. * TODO: get rid of this silly bit-multiplexing * * @see si.ijs.maci.ContainerOperations#shutdown(int) */ public void shutdown(int encryptedAction) { shutdown(encryptedAction, true, true); } /** * Shuts down the container. * <p> * Depending on the <code>gracefully</code> parameter, either {@link #deactivate_components(int[])} or * {@link #abortAllComponents(long)} is called. * <p> * This method may be called from the following threads: * <ul> * <li>one of the ORB invocation threads (shutdown initiated by the manager) * <li>shutdown thread (Ctrl-C VM hook) * </ul> * @param encryptedAction * ignored for the time being (always EXIT) * @param gracefully * if true, this method only returns after <code>cleanUp</code> has been called on all components. * if false, it returns faster, running the components' abort methods in separate threads for at most 3 seconds. * @see #shutdown(int) * @see ShutdownHook */ void shutdown(int encryptedAction, boolean gracefully, boolean isOrbThread) { int action = (encryptedAction >> 8) & 0xFF; m_logger.info("received call to 'shutdown', action=" + action + " (encryptedAction=" + encryptedAction + "), gracefully=" + gracefully + "."); if (shuttingDown.getAndSet(true)) { m_logger.fine("call to shutdown() while shutting down will be ignored..."); return; } m_managerProxy.shutdownNotify(); threadPoolExecutor.shutdown(); // shut down all active components if (gracefully) { try { deactivateAllComponents(); } catch (Exception ex) { m_logger.log(Level.WARNING, "Failed to deactivate components for graceful shutdown"); } } else { abortAllComponents(3000); } // shutdown alarm subsystem ACSAlarmSystemInterfaceFactory.done(); // shutdown BACI framework try { Class<?> clazz = Class.forName("alma.ACS.jbaci.BACIFramework"); Object baciFramework = clazz.getField("INSTANCE").get(null); clazz.getMethod("shutdown").invoke(baciFramework); } catch(Exception e) { m_logger.log(Level.WARNING, "Failed to graceful shutdown the BACI framework"); } m_managerProxy.logoutFromManager(); // shutdown logging subsystem if (!isEmbedded) { ClientLogManager.getAcsLogManager().shutdown(gracefully); // here the flag "wait_for_completion" is taken from "gracefully" // Note that it is not considered anyway if isOrbThread == true m_acsCorba.shutdownORB(gracefully, isOrbThread); } if (m_alarmContainerServices != null) { m_alarmContainerServices.cleanUp(); } // to allow creation of a new container in the same VM s_instance = null; } /** * Aborts all components that are not already aborting * or already defunct. * <p> * This method runs the components' <code>aboutToAbort</code> methods * concurrently in separate daemon threads. * It returns when all components have been aborted, or when the abortion * is timed out after <code>maxWaitTimeMillis</code>. * <p> * @param maxWaitTimeMillis maximum time in milliseconds spent in <code>aboutToAbort</code> of all components. * @see ComponentLifecycle#aboutToAbort() * @see ComponentAdapter#getComponentAbortionist(boolean) */ private synchronized void abortAllComponents(long maxWaitTimeMillis) { // just to verify the threading situation m_logger.info("will shut down all active components, thread=" + Thread.currentThread().getName()); boolean didAbort = false; ComponentAdapter[] compAdapters = m_activeComponentMap.getAllComponentAdapters(); // Current ad-hoc policy: // * half of the components (corePoolSize) will be aborted in freshly created threads. // * the other half will go in a queue, which is polled by the threads once they are done with their initial tasks. // Todo: think about a better policy to limit the max thread number. // For this we need experience with typical time demands for abort methods, vs. resource issues from excessive thread creation. int corePoolSize = compAdapters.length / 2 + 1; int maxThreadNumber = compAdapters.length + 1; // will not be needed with the current queue capacity (but important for "sick" cases like compAdapters.length == 1) int queueCapacity = compAdapters.length / 2 + 1; // From ThreadPoolExecutor API: // A bounded queue (for example, an ArrayBlockingQueue) helps prevent resource exhaustion when used with finite maximumPoolSizes, // but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: // Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to // artificially low throughput. // If tasks frequently block (for example if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. // Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, // which also decreases throughput. BlockingQueue<Runnable> execQueue = new ArrayBlockingQueue<Runnable>(queueCapacity); ExecutorService executor = new ThreadPoolExecutor(corePoolSize, maxThreadNumber, 60L, TimeUnit.SECONDS, execQueue, containerThreadFactory ); for (int i = 0; i < compAdapters.length; i++) { ComponentAdapter compAdapter = compAdapters[i]; ComponentStates state = compAdapter.getComponentStateManager().getCurrentState(); if (state != ComponentStates.COMPSTATE_ABORTING && state != ComponentStates.COMPSTATE_DEFUNCT ) { didAbort = true; // don't waste time to kill the component POA separately // (will be killed along with its parent POA "ComponentPOA" at shutdown) Runnable abortionist = compAdapter.getComponentAbortionist(false); try { // run in a separate thread executor.execute(abortionist); } catch (RejectedExecutionException e) { System.err.println("failed to abort component '" + compAdapter.getName() + "' because of exception " + e.getMessage()); } } } executor.shutdown(); boolean doneInTime = false; try { // block until all threads are finished or timeout occurs doneInTime = executor.awaitTermination(maxWaitTimeMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException ie) { // must go on } if (didAbort) { String msg = "done with aborting components "; if (doneInTime) { msg += "in time."; } else { msg += "(timed out)."; } m_logger.info(msg); // System.out.println(msg); } else { String msg = "no components needed to be aborted."; m_logger.info(msg); // System.out.println(msg); } } ///////////////////////////////////////////////////////////// // Implementation of ContainerOperations#get_component_info ///////////////////////////////////////////////////////////// /** * Returns information about a subset of components that are currently hosted by the Container. * Note: If the list of handles is empty, information about * all components hosted by the container is returned! * * @param handles * @return Information about the selected components. * @see si.ijs.maci.ContainerOperations#get_component_info(int[]) **/ public ComponentInfo[] get_component_info(int[] handles) { logManagerRequest("received call to get_component_info", handles); // get those ComponentInfos List<ComponentInfo> componentInfolist = new ArrayList<ComponentInfo>(); ComponentAdapter[] adapters = null; if (handles != null && handles.length > 0) { adapters = m_activeComponentMap.getComponentAdapters(handles); } else { adapters = m_activeComponentMap.getAllComponentAdapters(); } for (int i = 0; i < adapters.length; i++) { ComponentAdapter adapter = adapters[i]; componentInfolist.add(adapter.getComponentInfo()); } ComponentInfo[] ret = componentInfolist.toArray(new ComponentInfo[0]); return ret; } // /////////////////////////////////////////////////////////// // Implementation of ClientOperations methods // /////////////////////////////////////////////////////////// /** * @see si.ijs.maci.ClientOperations#name() */ public String name() { m_logger.fine("call to name() answered with '" + m_containerName + "'."); return m_containerName; } /** * Disconnect notification. The disconnect method is called by the Manager to notify the client that it will be * unavailable and that the client should log off. * <p> * Since ACS 7.0.2, this method returns quickly and uses a different thread to log back in with the manager. This is * cleaner than taking a thread from the ORB's pool for a possibly long time, although the manager does not care * because this method is defined as oneway in IDL. * * @see si.ijs.maci.ClientOperations#disconnect() */ public void disconnect() { m_logger.warning("Manager requests logout..."); m_managerProxy.logoutFromManager(); try { // to give the manager 10 sec time to go down or whatever Thread.sleep(10000); } catch (InterruptedException e1) { // ignore } // m_recoveryStart = true; // check discussion at http://almasw.hq.eso.org/almasw/bin/preview/ACS/NewMaciIdl Runnable reloginRunnable = new Runnable() { public void run() { try { // will loop until manager ref becomes available loginToManager(m_managerRetry); } catch (Exception e) { m_logger.log(Level.WARNING, "Failed to re-login to the manager. Will shut down.", e); // it could be that the connection failed exactly because we are shutting down, // and we want to avoid the warning from another shutdown call... if (!shuttingDown.get()) { shutdown(AcsContainer.CONTAINER_EXIT << 8, true, false); } } } }; containerThreadFactory.newThread(reloginRunnable).start(); } /** * Authentication method. * Below some passages from maci.idl: * <p> * Method authenticate is the challenge issued to the client after it tries to login. * The login will be successful if the client's authenticate() produces the expected result. * Only in this case will the Manager's login method return a valid handle, * which the client will later use as the id parameter with all calls to the Manager. * <p> * The first character of the answer identifies the type of the client, and can be one of: * <UL> * <LI><TT>C</TT> A regular client (implements just the Client interface).</LI> * <LI><TT>A</TT> A container (implements the Container interface).</LI> * <LI><TT>AR</TT> A container with recovery capability (implements the Container interface). </LI> * <LI><TT>S</TT> Supervisor (implements the Administrator interface).</LI> * </UL> * <p> * Container may support recovery. If the container terminates unexpectedly, * and then recovers (after a reboot, for example), it logs in to the Manager and notifies * it that it supports recovery by responding with "AR" to the Client::authenticate method. * The Manager then uses the activate_component method on the container to bring all the components * the container used to have back to life. * * @return Answer to the question. * @see si.ijs.maci.ClientOperations#authenticate(String) */ public AuthenticationData authenticate(long execution_id, String question) { // the old string-based answer is kept around for the time being String code = ( useRecoveryMode ? "AR" : "A" ); String answer = code + ' ' + m_containerName; // keep old executionId if it exists if (executionId < 0) { executionId = execution_id; } AuthenticationData ret = new AuthenticationData( answer, ClientType.CONTAINER_TYPE, ImplLangType.JAVA, useRecoveryMode, startTimeUTClong, executionId); return ret; } /** * @see si.ijs.maci.ClientOperations#message(short, String) */ public void message(short type, String message) { if (type == ClientOperations.MSG_ERROR) { m_logger.warning("Error message from the manager: " + message); } else if (type == ClientOperations.MSG_INFORMATION) { m_logger.info("Info message from the manager: " + message); } else { m_logger.info("Message of unknown type from the manager: " + message); } } /** * @see si.ijs.maci.ClientOperations#taggedmessage(short, short, String) */ public void taggedmessage(short type, short messageID, String message) { if (messageID == ClientOperations.MSGID_AUTOLOAD_START) { System.out.println(ContainerOperations.ContainerStatusCompAutoloadBeginMsg); } if (type == ClientOperations.MSG_ERROR) { m_logger.warning("Error message from the manager: " + message); } else if (type == ClientOperations.MSG_INFORMATION) { m_logger.info("Info message from the manager: " + message); } else { m_logger.info("Message of unknown type from the manager: " + message); } if (messageID == ClientOperations.MSGID_AUTOLOAD_END) { System.out.println(ContainerOperations.ContainerStatusCompAutoloadEndMsg); System.out.println(ContainerOperations.ContainerStatusReadyMsg); } } /** * Replies with <code>true</code> so that Manager sees that this container is alive. * <p> * Prints a message to System.out, so that it's visible on the local console that the container is doing ok. Does * not log anything, because a failure to reply would show up remotely anyway. * <p> * No message is printed if the container log level is above INFO, see http://jira.alma.cl/browse/COMP-1736. * * @see si.ijs.maci.ClientOperations#ping() */ public boolean ping() { // we cannot use m_logger.isLoggable because only the stdout log level should be considered, // and it would be even worse a hack to go from the logger to its stdout handler to ask for the level there. AcsLogLevelDefinition stdoutLevel; try { stdoutLevel = AcsLogLevelDefinition.fromXsdLogLevel(logConfig.getNamedLoggerConfig(m_containerName).getMinLogLevelLocal()); } catch (AcsJIllegalArgumentEx ex) { stdoutLevel = AcsLogLevelDefinition.DEBUG; ex.printStackTrace(); } if (stdoutLevel.compareTo(AcsLogLevelDefinition.DEBUG) <= 0) { Runtime rt = Runtime.getRuntime(); long totalMemKB = rt.totalMemory() / 1024; long usedMemKB = totalMemKB - rt.freeMemory() / 1024; String memStatus = "Memory usage " + usedMemKB + " of " + totalMemKB + " kB "; long maxMem = rt.maxMemory(); if (maxMem < Long.MAX_VALUE) { long maxMemKB = maxMem / 1024; memStatus += "(= " + (usedMemKB * 1000 / maxMemKB) / 10.0 + "% of JVM growth limit " + maxMemKB + " kB) "; } String timestamp = IsoDateFormat.formatCurrentDate(); System.out.println(timestamp + " [" + m_containerName + "] ping received, container alive. " + memStatus); } // ThreadMXBean threadMX = ManagementFactory.getThreadMXBean(); // threadMX. return true; } /** * Notify client about the change (availability) of the components currently in use by this client. * * @see si.ijs.maci.ClientOperations#components_available(ComponentInfo[]) */ public void components_available(ComponentInfo[] components) { if (components == null) { return; } StringBuffer buff = new StringBuffer("components_available: "); for (int i = 0; i < components.length; i++) { buff.append(components[i].name).append(" "); } m_logger.fine(buff.toString()); // have containerservices instances notify their clients List<ComponentDescriptor> compDescs = ComponentDescriptor.fromComponentInfoArray(components); for (ComponentAdapter compAd : m_activeComponentMap.getAllComponentAdapters()) { compAd.getContainerServices().fireComponentsAvailable(compDescs); } } /** * Notify client that some of the components currently in use by client * have become unavailable. * @see si.ijs.maci.ClientOperations#components_unavailable(String[]) */ public void components_unavailable(String[] component_names) { if (component_names == null) { return; } StringBuffer buff = new StringBuffer("components_unavailable: "); for (int i = 0; i < component_names.length; i++) { buff.append(component_names[i]).append(" "); } m_logger.fine(buff.toString()); // have containerservices instances notify their clients List<String> compNames = Arrays.asList(component_names); for (ComponentAdapter compAd : m_activeComponentMap.getAllComponentAdapters()) { compAd.getContainerServices().fireComponentsUnavailable(compNames); } } ///////////////////////////////////////////////////////////// // other (util stuff) ///////////////////////////////////////////////////////////// /** * Logs a request from the manager which involves an array of component handles. * (which is quite different from a LogManager request...) * <p> * Checks if the handles are valid. * If no component can be found for a given handle, the string <code>(unknown) </code> * is appended to that handle. * <p> * Used simply to avoid code duplication. * * @param msgBegin begin of the log message * @param handles handles to components that this container should know of */ private void logManagerRequest(String msgBegin, int[] handles) { if (handles != null) { StringBuffer buff = new StringBuffer(msgBegin + "; handles = "); for (int i = 0; i < handles.length; i++) { buff.append(handles[i]); ComponentAdapter compAdapter = m_activeComponentMap.get(handles[i]); if (compAdapter == null) { buff.append("(unknown) "); } else { buff.append(' '); } } m_logger.fine(buff.toString()); } else { m_logger.fine(msgBegin); } } // /////////////////////////////////////////////////////////// // LoggingConfigurable interface // /////////////////////////////////////////////////////////// /** * Gets the log levels of the default logging configuration. These levels * are used by all loggers that have not been configured individually. */ public LogLevels get_default_logLevels() { tryToWaitForContainerStart(); LogLevels logLevels = new LogLevels(); logLevels.useDefault = false; logLevels.minLogLevel = (short) logConfig.getDefaultMinLogLevel().value; logLevels.minLogLevelLocal = (short) logConfig.getDefaultMinLogLevelLocal().value; return logLevels; } /** * Sets the log levels of the default logging configuration. These levels * are used by all loggers that have not been configured individually. */ public void set_default_logLevels(LogLevels levels) throws IllegalLogLevelsEx { tryToWaitForContainerStart(); try { logConfig.setDefaultMinLogLevel(AcsLogLevelDefinition.fromInteger(levels.minLogLevel)); logConfig.setDefaultMinLogLevelLocal(AcsLogLevelDefinition.fromInteger(levels.minLogLevelLocal)); } catch (AcsJIllegalArgumentEx ex) { //throw ex.toIllegalArgumentEx(); IllegalLogLevelsEx ille = new IllegalLogLevelsEx(ex.getErrorDesc()); throw ille; } } /** * Gets the names of all loggers, to allow configuring their levels * individually. The names are those that appear in the log records in the * field "SourceObject". This includes the container logger, ORB logger, * component loggers, and (only C++) GlobalLogger. * <p> * The returned logger names are randomly ordered. */ public String[] get_logger_names() { tryToWaitForContainerStart(); Set<String> loggerNames = logConfig.getLoggerNames(); return loggerNames.toArray(new String[loggerNames.size()]); } /** * Gets log levels for a particular named logger. If the returned field * LogLevels.useDefault is true, then the logger uses the default levels, * see get_default_logLevels(); otherwise the returned local and remote * levels apply. * <p> * For possible convenience, the default levels are returned in addition to * setting {@link LogLevels#useDefault} to <code>true</code>. */ public LogLevels get_logLevels(String logger_name) throws LoggerDoesNotExistEx { tryToWaitForContainerStart(); UnnamedLogger xsdLevels = logConfig.getNamedLoggerConfig(logger_name); boolean useDefault = !logConfig.hasCustomConfig(logger_name); LogLevels ret = AcsLogLevelDefinition.createIdlLogLevelsFromXsd(useDefault, xsdLevels); return ret; } /** * Sets log levels for a particular named logger. If levels.useDefault is * true, then the logger will be reset to using default levels; otherwise it * will use the supplied local and remote levels. */ public void set_logLevels(String logger_name, LogLevels levels) throws LoggerDoesNotExistEx, IllegalLogLevelsEx { tryToWaitForContainerStart(); if (levels.useDefault) { logConfig.clearNamedLoggerConfig(logger_name); } else { try { UnnamedLogger config = AcsLogLevelDefinition.createXsdLogLevelsFromIdl(levels); logConfig.setNamedLoggerConfig(logger_name, config); } catch (AcsJIllegalArgumentEx ex) { //throw ex.toIllegalArgumentEx(); IllegalLogLevelsEx ille = new IllegalLogLevelsEx(ex.getErrorDesc()); throw ille; } } } /** * Commands the container or manager to read in again the logging * configuration from the CDB and to reconfigure the loggers accordingly. * This allows for persistent changes in the logging configuration to become * effective, and also for changes of more advanced parameters. * <p> * Note that unlike for the logging initialization in {@link #initialize()}, * now we give precedence to the CDB values over any previous settings. */ public void refresh_logging_config() { tryToWaitForContainerStart(); try { logConfig.initialize(true); } catch (LogConfigException ex) { // if the CDB can't be read, we still want to run the container, thus we only log the problem here m_logger.log(Level.FINE, "Failed to configure logging (default values will be used).", ex); } } // /////////////////////////////////////////////////////////// // ACSLogStatistics interface // /////////////////////////////////////////////////////////// /** * Gets the names and status of all statistics modules of all loggers, to allow configuring them individually. * If the logger statistics module has never been configured yet, then it will provide "Undefined" * as elementName */ public LogStatsInformation[] get_statistics_logger_configuration() { tryToWaitForContainerStart(); // Temporal list to store logger information ArrayList<LogStatsInformation> statsInfoList = new ArrayList<LogStatsInformation>(); // Retrieve logger names Set<String> loggerNames = logConfig.getLoggerNames(); // Retrieve actual loggers information Iterator iterator = loggerNames.iterator(); while(iterator.hasNext()) { // Get logger item String name = (String) iterator.next(); AcsLogger retrievedLogger = ClientLogManager.getAcsLogManager().getLoggerByName(name); // Store relevant logger information statsInfoList.add(new LogStatsInformation(retrievedLogger.stats.getStatisticsIdentification(), retrievedLogger.getName(), retrievedLogger.stats.getDisableStatistics(), retrievedLogger.stats.getStatisticsCalculationPeriod(), retrievedLogger.stats.getStatisticsGranularity() )); } // Structure from IDL to store retrieved logger statistics module relevant information LogStatsInformation[] statsInfoSeq = new LogStatsInformation [statsInfoList.size()]; // Conversion from temp list to array statsInfoSeq = statsInfoList.toArray(statsInfoSeq); return statsInfoSeq; } /** * Gets the names and status of statistics module of requested logger. * If the logger statistics module has never been configured yet, then it will provide "Undefined" * as elementName * Throws LoggerDoesNotExistEx if a the logger is not found */ public LogStatsInformation get_statistics_logger_configuration_byname(String logger_name) throws alma.Logging.LoggerDoesNotExistEx { // Retrieve logger by logger name AcsLogger retrievedLogger = ClientLogManager.getAcsLogManager().getLoggerByName(logger_name); // Verification if logger does exist (!= null) if (retrievedLogger == null) { LoggerDoesNotExistEx inexistantLoggerEx = new LoggerDoesNotExistEx(logger_name); throw inexistantLoggerEx; } // Store logger statistics configuration LogStatsInformation statsInfo = new LogStatsInformation(retrievedLogger.stats.getStatisticsIdentification(), retrievedLogger.getName(), retrievedLogger.stats.getDisableStatistics(), retrievedLogger.stats.getStatisticsCalculationPeriod(), retrievedLogger.stats.getStatisticsGranularity() ); return statsInfo; } /** * Sets logger statistics configuration for a particular named logger. * Throws LoggerDoesNotExistEx if a the logger is not found */ public void set_statistics_logger_configuration_byname(String logger_name, LogStatsInformation statsInformation) throws alma.Logging.LoggerDoesNotExistEx { // Retrieve logger by logger name AcsLogger retrievedLogger = ClientLogManager.getAcsLogManager().getLoggerByName(logger_name); // Verification if logger does exist (!= null) if (retrievedLogger == null) { LoggerDoesNotExistEx inexistantLoggerEx = new LoggerDoesNotExistEx(logger_name); throw inexistantLoggerEx; } // Configure logger retrievedLogger.stats.configureStatistics(statsInformation.statsId, statsInformation.statsStatus, statsInformation.statsPeriodConfiguration, statsInformation.statsGranularityConfiguration); return; } /** ************************ END LoggingConfigurable ************************ */ /** * Waits for the container to finish startup, see {@link #containerStartOrbThreadGate}. * Does not expose InterruptedException, so that in case of such an exception * this method will return without the container having finished its startup. */ protected void tryToWaitForContainerStart() { try { containerStartOrbThreadGate.await(30, TimeUnit.SECONDS); } catch (InterruptedException ex1) { // ignore } } }