/*
* 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.IOException;
import java.util.logging.Level;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.Servant;
import com.cosylab.CDB.DAL;
import si.ijs.maci.ComponentInfo;
import alma.ACS.ComponentStates;
import alma.JavaContainerError.wrappers.AcsJContainerEx;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.classloading.AcsComponentClassLoader;
import alma.acs.component.ComponentLifecycle;
import alma.acs.component.ComponentLifecycleException;
import alma.acs.container.corba.AcsCorba;
import alma.acs.logging.AcsLogger;
import alma.maciErrType.ComponentDeactivationUncleanEx;
import alma.maciErrType.wrappers.AcsJComponentDeactivationFailedEx;
import alma.maciErrType.wrappers.AcsJComponentDeactivationUncleanEx;
/**
* Adapter between the container on the one side and a component with all its child objects on the other.
* For the component, <code>ComponentAdapter</code> is the container,
* since it calls the <code>ComponentLifecycle</code> methods.
* For the container, it represents the component with its meta data.
*
* @author hsommer Nov 6, 2002 2:48:13 PM
*/
public class ComponentAdapter
{
// fields from si.ijs.maci.ComponentInfo
private String m_type;
private String m_code;
private org.omg.CORBA.Object m_reference; // the corba object
private String m_compInstanceName;
private int[] m_clients;
private String m_containerName;
private int m_compHandle;
private int m_access;
private String[] m_interfaces;
// other fields
private AcsLogger m_containerLogger;
// the component itself and its XML translator proxy, if any
private ComponentLifecycle m_component;
// the tie skeleton that receives ORB calls
private Servant m_servant;
// the POA to be used for this component
private POA m_componentPOA;
// the servant manager for this component POA
private ComponentServantManager compServantManager;
private final AcsCorba acsCorba;
private final AcsManagerProxy m_managerProxy;
private final ClassLoader m_componentClassLoader;
private final ComponentStateManagerImpl m_componentStateManager;
private final ContainerServicesImpl m_containerServices;
private final CleaningDaemonThreadFactory m_threadFactory;
/**
* Method ComponentAdapter.
* @param compName component instance name (curl)
* @param type IDL type
* @param code Java impl class of the component <b>helper</b> (subclass of {@link ComponentHelper});
* @param compHandle component handle assigned by the manager
* @param containerName
* @param component the instance of the component implementation class
* @param managerProxy the shared manager proxy object
* @param componentClassLoader the classloader to be used as the currentThread-ClassLoader when component lifecycle methods are invoked.
* @param logger logger to be used by this class (not by the component though)
* @param acsCorba
* @throws AcsJContainerServicesEx
*/
ComponentAdapter(String compName, String type, String code,
int compHandle, String containerName,
ComponentLifecycle component,
AcsManagerProxy managerProxy, DAL cdb,
ClassLoader componentClassLoader,
AcsLogger logger,
AcsCorba acsCorba)
throws AcsJContainerEx
{
// store params
m_compInstanceName = compName;
m_type = type;
m_code = code;
m_compHandle = compHandle;
m_containerName = containerName;
m_component = component;
m_componentClassLoader = componentClassLoader;
m_containerLogger = logger;
this.acsCorba = acsCorba;
// init arrays to avoid nullpointer trouble
m_interfaces = new String[0];
m_clients = new int[0];
m_componentPOA = acsCorba.createPOAForComponent(m_compInstanceName);
m_componentStateManager = new ComponentStateManagerImpl(m_compInstanceName, m_containerLogger);
m_threadFactory = new CleaningDaemonThreadFactory(compName, m_containerLogger);
m_threadFactory.setNewThreadContextClassLoader(m_componentClassLoader);
m_managerProxy = managerProxy;
m_containerServices =
new ContainerServicesImpl(managerProxy, cdb, m_componentPOA, acsCorba, m_containerLogger,
m_compHandle, m_compInstanceName, m_componentStateManager, m_threadFactory);
}
void setComponentXmlTranslatorProxy(Object xmlTranslatorProxy) {
m_containerServices.setComponentXmlTranslatorProxy(xmlTranslatorProxy);
}
ContainerServicesImpl getContainerServices() {
return m_containerServices;
}
void activateComponent(Servant servant)
throws AcsJContainerEx
{
if (m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.finer("entering ComponentAdapter#activateComponent for " + m_compInstanceName);
}
m_servant = servant;
try {
compServantManager = acsCorba.setServantManagerOnComponentPOA(m_componentPOA);
m_reference = acsCorba.activateComponent(servant, m_compInstanceName, m_componentPOA);
}
catch (Throwable thr) {
String msg = "failed to activate component " + m_compInstanceName + " of type " + m_component.getClass().getName();
AcsJContainerEx ex = new AcsJContainerEx(thr);
ex.setContextInfo(msg);
throw ex;
}
m_interfaces = _getInterfaces();
}
void initializeComponent() throws ComponentLifecycleException {
ClassLoader contCL = Thread.currentThread().getContextClassLoader();
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_INITIALIZING);
Throwable thr = null;
Thread.currentThread().setContextClassLoader(m_componentClassLoader);
try {
m_component.initialize(m_containerServices);
} catch (Throwable t) {
thr = t;
} finally {
Thread.currentThread().setContextClassLoader(contCL);
}
if (thr != null) {
if (thr instanceof ComponentLifecycleException) {
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_ERROR);
throw (ComponentLifecycleException) thr;
}
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_ERROR);
throw new ComponentLifecycleException(thr);
}
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_INITIALIZED);
}
void executeComponent() throws ComponentLifecycleException {
ClassLoader contCL = Thread.currentThread().getContextClassLoader();
Throwable thr = null;
Thread.currentThread().setContextClassLoader(m_componentClassLoader);
try {
m_component.execute();
} catch (Throwable t) {
thr = t;
} finally {
Thread.currentThread().setContextClassLoader(contCL);
}
if (thr != null) {
if (thr instanceof ComponentLifecycleException) {
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_ERROR);
throw (ComponentLifecycleException) thr;
}
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_ERROR);
throw new ComponentLifecycleException(thr);
}
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_OPERATIONAL);
}
private String[] _getInterfaces()
{
String[] interfaces = null;
try
{
interfaces = m_servant._all_interfaces(m_componentPOA, m_compInstanceName.getBytes());
if (m_containerLogger.isLoggable(Level.FINE)) {
StringBuffer buff = new StringBuffer("");
for (int i = 0; i < interfaces.length; i++)
{
buff.append(interfaces[i]);
}
m_containerLogger.fine("interfaces of component '" + m_compInstanceName + "': " + buff.toString());
}
}
catch (Exception ex)
{
m_containerLogger.info("failed to retrieve interface information for component " +
m_compInstanceName);
}
if (interfaces == null)
{
interfaces = new String[1];
interfaces[0] = "IDL:omg.org/CORBA/Object:1.0";
}
return interfaces;
}
/**
* Deactivates a component.
* <ol>
* <li>First the component's POA manager is put into inactive state, so that all incoming calls to this component are rejected.
* However, we wait for currently executing calls to finish, with a timeout as described below.
* <ul>
* <li>Rejection applies to requests already received and queued by the ORB (but that have not started executing),
* as well as to requests that clients will send in the future.
* <li>Note that entering into the inactive state may take forever if the component hangs in a functional call.
* <li>Therefore we use a timeout to proceed in such cases where POA manager deactivation does not happen in time.
* This bears the risk of undesirable behavior caused by calling the {@link ComponentLifecycle#cleanUp() cleanUp}
* method while other threads still perform functional calls on the component.
* </ul>
* <li>Second the component itself is deactivated:
* <ul>
* <li>The lifecycle method {@link ComponentLifecycle#cleanUp() cleanUp} is called, currently without enforcing a timeout.
* <li>TODO: use a timeout, unless we decide that a client-side timeout for releaseComponent is good enough.
* </ul>
* <li>Third the component is disconnected from CORBA ("etherealized" from the POA).
* <ul>
* <li>Note that also etherealization may take forever if the component hangs in a call.
* <li>Therefore we use a timeout to proceed with deactivation in such cases where etherealization does not happen in time.
* <li>Currently a component that failed to etherealize in time can stay active as long as the container is alive.
* TODO: check if using the "container sealant" we can identify and stop the active ORB threads.
* </ul>
* </ol>
* This method logs errors as FINER if they also cause an exception, and as WARNING if they cannot lead to an exception
* because other more important error conditions are present.
*
* @throws ComponentDeactivationUncleanEx, ComponentDeactivationFailedEx
*/
void deactivateComponent() throws AcsJComponentDeactivationUncleanEx, AcsJComponentDeactivationFailedEx {
if (m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.finer("About to deactivate component " + m_compInstanceName + " with handle " + getHandle());
}
AcsJComponentDeactivationUncleanEx deactivationUncleanEx = null;
AcsJComponentDeactivationFailedEx deactivationFailedEx = null;
try
{
// (1) try to reject calls by sending poa manager to inactive state
// TODO: make the timeout configurable
int deactivateTimeoutMillis = 10000;
boolean isInactive = acsCorba.deactivateComponentPOAManager(m_componentPOA, m_compInstanceName, deactivateTimeoutMillis);
if (isInactive && m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.finer("Now rejecting any calls to component '" + m_compInstanceName + "'. Will call cleanUp() next.");
}
else if (!isInactive) {
String msg = "Component '" + m_compInstanceName + "' failed to reject calls within " +
deactivateTimeoutMillis + " ms, probably because of pending calls. Will call cleanUp() anyway.";
m_containerLogger.warning(msg);
deactivationUncleanEx = new AcsJComponentDeactivationUncleanEx();
deactivationUncleanEx.setCURL(m_compInstanceName);
deactivationUncleanEx.setReason(msg);
// do not yet throw deactivationUncleanEx as we need to go through the other steps first
}
// (2) call the lifecycle method cleanUp and also clean container services and other support classes
ClassLoader contCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(m_componentClassLoader);
try {
// TODO: also use a timeout for cleanUp
m_component.cleanUp();
} catch (Throwable thr) {
// AcsJComponentCleanUpEx is declared, but any other ex will be wrapped by AcsJComponentDeactivationUncleanEx as well
m_containerLogger.log(Level.FINE, "Failure in cleanUp() method of component " + m_compInstanceName, thr);
deactivationUncleanEx = new AcsJComponentDeactivationUncleanEx(thr); // this would override a previous ex from POA deactivation
deactivationUncleanEx.setCURL(m_compInstanceName);
// do not yet throw deactivationUncleanEx as we need to nonetheless destroy the POA
} finally {
Thread.currentThread().setContextClassLoader(contCL);
try {
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_DEFUNCT);
} catch (ComponentLifecycleException ex) {
if (deactivationUncleanEx == null) { // an ex from cleanUp would be more important
deactivationUncleanEx = new AcsJComponentDeactivationUncleanEx(ex);
deactivationUncleanEx.setCURL(m_compInstanceName);
}
else {
m_containerLogger.log(Level.WARNING, "Failed to set component state DEFUNCT on " + m_compInstanceName, ex);
}
}
m_containerServices.cleanUp();
m_threadFactory.cleanUp();
}
// (3) destroy the component POA
// since we already tried to discard requests using the poa manager before,
// the additional timeout can be kept small. If calls are pending, we fail.
int etherealizeTimeoutMillis = 1000;
boolean isEtherealized = acsCorba.destroyComponentPOA(m_componentPOA, compServantManager, etherealizeTimeoutMillis);
if (isEtherealized && m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.finer("Component '" + m_compInstanceName + "' is etherealized.");
}
else if (!isEtherealized){
m_containerLogger.warning("Component '" + m_compInstanceName + "' failed to be etherealized in " +
etherealizeTimeoutMillis + " ms, probably because of pending calls.");
deactivationFailedEx = new AcsJComponentDeactivationFailedEx();
deactivationFailedEx.setCURL(m_compInstanceName);
deactivationFailedEx.setReason("Component POA etherialization timed out after " + etherealizeTimeoutMillis + " ms.");
deactivationFailedEx.setIsPermanentFailure(true); // @TODO: distinguish the cases better
// do not yet throw deactivationFailedEx as we need to nonetheless close the classloader
}
// (4) "close" m_componentClassLoader (otherwise JVM native mem leak, see COMP-4929)
if (m_componentClassLoader instanceof AcsComponentClassLoader) {
try {
((AcsComponentClassLoader)m_componentClassLoader).close();
} catch (IOException ex) {
m_containerLogger.log(Level.WARNING, "Failed to close component class loader", ex);
}
}
}
catch (RuntimeException ex) {
if (deactivationFailedEx == null) { // exception from POA destruction has precedence
deactivationFailedEx = new AcsJComponentDeactivationFailedEx(ex);
deactivationFailedEx.setCURL(m_compInstanceName);
deactivationFailedEx.setReason("Unexpected exception caught during component deactivation.");
}
else {
m_containerLogger.log(Level.WARNING, "Unexpected exception caught during deactivation of component " + m_compInstanceName, ex);
}
}
if (deactivationFailedEx != null) {
if (m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.log(Level.FINER, "Deactivation of component " + m_compInstanceName + " failed. "
+ "Will throw AcsJComponentDeactivationFailedEx", deactivationFailedEx);
}
throw deactivationFailedEx;
}
if (deactivationUncleanEx != null) {
if (m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.log(Level.FINER, "Deactivation of component " + m_compInstanceName + " finished with problems. "
+ "Will throw AcsJComponentDeactivationUncleanEx", deactivationUncleanEx);
}
throw deactivationUncleanEx;
}
if (m_containerLogger.isLoggable(Level.FINER)) {
m_containerLogger.finer("Done deactivating component " + m_compInstanceName + " with handle " + getHandle());
}
}
/**
* Returns a <code>Runnable</code> that can abort the component in the following way.
* <ol>
* <li>Sets the component state to <code>ABORTING</code>
* <li>Calls {@link ComponentLifecycle#aboutToAbort()}
* <li>Sets the component state to <code>DEFUNCT</code>
* <li>Kills all surviving user threads created by the component using {@link ContainerServices#getThreadFactory() getThreadFactory}
* <li>if <code>killComponentPOA==true</code>, destroys the POA for this component;
* </ol>
* This method returns immediately, so the caller can then run the returned Runnable in its own thread.
*/
Runnable getComponentAbortionist(final boolean killComponentPOA) {
Runnable abortionist = new Runnable() {
public void run() {
try {
// todo: check how time consuming this POA deactivation is,
// and if it should be postponed till after aboutToAbort()
// ... seems we don't need this at all!
// m_componentPOA.deactivate_object(m_compInstanceName.getBytes());
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_ABORTING);
ClassLoader contCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(m_componentClassLoader);
try {
m_component.aboutToAbort();
} finally {
Thread.currentThread().setContextClassLoader(contCL);
m_componentStateManager.setStateByContainer(ComponentStates.COMPSTATE_DEFUNCT);
m_containerServices.cleanUp();
m_threadFactory.cleanUp();
if (killComponentPOA) {
m_componentPOA.destroy(false, false);
}
}
} catch (Throwable t) {
m_containerLogger.log(Level.INFO, "failed to abort component " + getName(), t);
t.printStackTrace();
}
}
};
return abortionist;
}
ComponentInfo getComponentInfo()
{
ComponentInfo ci = new ComponentInfo(
m_type, m_code, m_reference, m_compInstanceName, m_clients, m_managerProxy.getManagerHandle(), m_containerName, m_compHandle, m_access, m_interfaces);
return ci;
}
/**
* todo: check with rest of ACS which fields really make a component unique in the system.
* Seems kind of undefined.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj)
{
if (obj == null || !(obj instanceof ComponentAdapter))
{
return false;
}
ComponentAdapter other = (ComponentAdapter) obj;
return (
m_compInstanceName.equals(other.getName()) &&
m_compHandle == other.getHandle() &&
m_type == other.getType()
);
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
return m_compInstanceName.hashCode();
}
/**
* Returns the reference.
* @return org.omg.CORBA.Object
*/
public org.omg.CORBA.Object getReference()
{
return m_reference;
}
/**
* Returns the handle.
* @return int
*/
public int getHandle()
{
return m_compHandle;
}
/**
* Returns the name.
* @return String
*/
public String getName()
{
return m_compInstanceName;
}
/**
* Returns the type.
* @return String
*/
public String getType()
{
return m_type;
}
/**
* To be called by the container to change the component state.
* In some cases, the state will be changed by this ComponentAdapter though.
*
* @return the <code>ComponentStateManager</code> that gives acces to the state.
*/
ComponentStateManagerImpl getComponentStateManager()
{
return m_componentStateManager;
}
// @TODO
// /**
// * @return true if the component managed by this adapter declares itself to be stateless.
// */
// boolean isStatelessComponent() {
// return ( m_component instanceof StatelessComponentLifecycle );
// }
protected void finalize() throws Throwable {
if (m_containerLogger.isLoggable(Level.FINEST)) {
m_containerLogger.finest("finalize() called by JVM for impl classes of component " + getName() + '(' + getHandle() + ')');
}
super.finalize();
}
/**
* With this optional call, automatic invocation logging for certain component methods can be disabled.
* (Data will just be forwarded to containerServices)
* @param excludedMethods
* @see ComponentHelper#getComponentMethodsExcludedFromInvocationLogging()
*/
void setMethodsExcludedFromInvocationLogging(String[] excludedMethods) {
m_containerServices.setMethodsExcludedFromInvocationLogging(excludedMethods);
}
}