/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (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 acs.benchmark.util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import si.ijs.maci.ComponentSpec;
import alma.ACS.ACSComponentOperations;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.component.ComponentLifecycle;
import alma.acs.component.ComponentLifecycleException;
import alma.acs.container.ContainerServices;
import alma.acs.container.ContainerServices.ComponentReleaseCallback;
import alma.acs.container.ContainerServices.ComponentReleaseCallbackWithLogging;
import alma.acs.logging.AcsLogLevel;
/**
* A reflection-based convenience class that retrieves components either from the container services
* or from its own component cache.
* <p>
* Features:
* <ul>
* <li>It restricts the interface to the IDL-derived "xxxOperations" class in order to allow
* unit testing with mock components or actual component impl classes, without requiring container/corba support.
* <li>Hides the Corba helper class that performs the "narrow". Together with the above point, corba is then completely hidden.
* <li>(TODO) It optionally installs a transparent client-side interceptor that detects runtime problems.
* <li>(TODO) Configurable timeout disconnect from components, with transparent reconnect.
* </ul>
* @TODO: To identify containers/hosts, use collocated dummy target components instead of the deprecated
* {@link ContainerServices#getDynamicComponent(ComponentSpec, boolean)}.
* <p>
* This class is thread-safe, so that a client can activate or release several components at once.
*/
public class ComponentAccessUtil {
protected final Logger logger;
protected final ContainerServices contSrv;
/**
* Component reference plus synchronization support for other threads
* that request the same component reference but must wait until the shared component
* has been activated.
*/
protected static class CompRefHelper {
ACSComponentOperations compRef;
final CountDownLatch activationSync;
boolean isReleasing;
CompRefHelper() {
this.activationSync = new CountDownLatch(1);
isReleasing = false;
}
/**
* Should be called by the first thread that requested the component,
* to unblock the other threads.
*/
void setCompRef(ACSComponentOperations compRef) {
this.compRef = compRef;
activationSync.countDown();
}
boolean awaitCompActivation(long timeout, TimeUnit unit) throws InterruptedException {
return activationSync.await(timeout, unit);
}
}
/**
* key = component instance name,
* value = component reference + sync support for other threads that request the same component.
* <p>
* This map must be protected from concurrent access.
*/
protected final Map<String, CompRefHelper> compName2Comp;
public ComponentAccessUtil(ContainerServices contSrv) {
this.contSrv = contSrv;
this.logger = contSrv.getLogger();
this.compName2Comp = new HashMap<String, CompRefHelper>();
}
/**
* Returns the cached reference or the component retrieved from the container services
* @param <T> The IDL-derived component interface
* @param compSpec
* @param idlOpInterface
* @return
* @throws AcsJContainerServicesEx
*/
public <T extends ACSComponentOperations> T getDynamicComponent(ComponentSpec compSpec, Class<T> idlOpInterface)
throws AcsJContainerServicesEx {
T comp = null;
boolean foundInCache = false;
boolean mustActivateComp = false;
CompRefHelper compRefHelper = null;
synchronized (compName2Comp) {
// try the cache first
compRefHelper = compName2Comp.get(compSpec.component_name);
if (compRefHelper == null) {
mustActivateComp = true;
compRefHelper = new CompRefHelper();
compName2Comp.put(compSpec.component_name, compRefHelper);
}
else {
if (compRefHelper.compRef != null) {
if (compRefHelper.isReleasing) {
AcsJContainerServicesEx ex = new AcsJContainerServicesEx();
ex.setContextInfo("Component '" + compSpec.component_name + "' is being released and thus cannot be activated.");
throw ex;
}
foundInCache = true;
comp = idlOpInterface.cast(compRefHelper.compRef);
}
}
} // must keep this synchronized block short, to not prevent parallel activation of different components!
try {
if (!foundInCache) {
if (mustActivateComp) {
comp = getDynamicComponentFromContainerServices(compSpec, idlOpInterface);
compRefHelper.setCompRef(comp); // this will free other threads waiting for the same comp.
}
else {
// wait if another thread is already activating this comp
boolean waitOK = false;
try {
waitOK = compRefHelper.awaitCompActivation(5, TimeUnit.MINUTES);
} catch (InterruptedException ex) {
// just leave waitOK = false
}
if (!waitOK) {
AcsJContainerServicesEx ex = new AcsJContainerServicesEx();
ex.setContextInfo("Timed out or got interrupted while waiting for activation of component '" +
compSpec.component_name + "'.");
throw ex;
}
}
}
return comp;
} finally {
logger.log(AcsLogLevel.DEBUG, "Retrieved component '" + compSpec.component_name + "' from "
+ (foundInCache ? "cache." : "container services.") );
}
}
/**
* Gets the component via {@link #contSrv}, without using the cache.
* Can be overridden for tests, to bypass the container services.
* @throws AcsJContainerServicesEx
*/
protected <T extends ACSComponentOperations> T getDynamicComponentFromContainerServices(ComponentSpec compSpec, Class<T> idlOpInterface)
throws AcsJContainerServicesEx {
// infer the corba helper class
Class<?> corbaHelperClass = null;
try {
int classBaseNameEnd = idlOpInterface.getName().lastIndexOf("Operations");
String classBaseName = idlOpInterface.getName().substring(0, classBaseNameEnd);
corbaHelperClass = Class.forName(classBaseName + "Helper");
} catch (Exception ex) {
String msg = "Failed to find Corba Helper class matching " + idlOpInterface.getName();
logger.log(Level.FINE, msg, ex);
throw new IllegalArgumentException(msg, ex); // TODO throw better ex
}
org.omg.CORBA.Object compRaw = contSrv.getDynamicComponent(compSpec, false);
// narrow the component reference
Object comp = null;
try {
Method narrowMethod = corbaHelperClass.getMethod("narrow", org.omg.CORBA.Object.class);
comp = narrowMethod.invoke(null, compRaw);
} catch (Exception ex) {
String msg = "Failed to Corba-narrow component " + compSpec.component_name;
logger.log(Level.FINE, msg, ex);
throw new IllegalArgumentException(msg, ex); // TODO throw better ex
}
if (!idlOpInterface.isInstance(comp)) {
String msg = "Narrowed component " + compSpec.component_name + " is not of type " + idlOpInterface;
logger.log(Level.FINE, msg);
throw new IllegalArgumentException(msg); // TODO throw better ex
}
return idlOpInterface.cast(comp);
}
/**
* Lists the names of the component instances that have already been retrieved.
*/
public List<String> getCachedComponentNames() {
List<String> ret = new ArrayList<String>();
synchronized (compName2Comp) {
for (String name : compName2Comp.keySet()) {
ret.add(name);
}
}
return ret;
}
public List<ACSComponentOperations> getCachedComponents() {
List<ACSComponentOperations> ret = new ArrayList<ACSComponentOperations>();
synchronized (compName2Comp) {
for (CompRefHelper compRefHelper : compName2Comp.values()) {
ret.add(compRefHelper.compRef);
}
}
return ret;
}
/**
* Should be called when a component is no longer needed.
*
* @param waitForCompRelease If <code>true</code> this method waits for the complete component release
* otherwise returns immediately
*
*/
public void releaseComponent(String compName, boolean waitForCompRelease) {
// Mark this comp in the cache
synchronized (compName2Comp) {
CompRefHelper compRefHelper = compName2Comp.get(compName);
if (compRefHelper != null) {
compRefHelper.isReleasing = true;
}
}
ComponentReleaseCallback callback = new ComponentReleaseCallbackWithLogging(logger, AcsLogLevel.DEBUG);
contSrv.releaseComponent(compName, callback);
try {
if (waitForCompRelease) {
boolean waitOK = callback.awaitComponentRelease(60, TimeUnit.SECONDS);
if (!waitOK) {
logger.warning("Timed out (60s) waiting for release of component " + compName);
}
}
} catch (InterruptedException ex) {
logger.log(AcsLogLevel.DEBUG, "Interrupted while waiting for release of component " + compName);
}
// remove comp reference from the cache
synchronized (compName2Comp) {
compName2Comp.remove(compName);
}
}
/**
* Should be called when none of the components are needed any more.
*
* @param waitForCompsRelease
* If <code>true</code> this method waits for the complete components release, otherwise returns immediately.
*/
public void releaseAllComponents(boolean waitForCompsRelease) {
List<String> compNames;
synchronized (compName2Comp) {
compNames = new ArrayList<String>(compName2Comp.keySet()); // to avoid ConcurrentModificationException
}
releaseComponents(compNames, waitForCompsRelease);
}
/**
* Releases a set of components sequentially.
* <p>
* Subclasses may override this method, see for example {@link ConcurrentComponentAccessUtil#releaseComponents(Collection, boolean)},
* to implement concurrent component release calls, which then also affects {@link #releaseAllComponents(boolean)}.
*
* @param componentNames The name of the components to release
* @param waitForCompsRelease If <code>true</code> this method waits for the complete components release
* otherwise returns immediately
*/
public void releaseComponents(Collection<String> componentNames, boolean waitForCompsRelease) {
if (componentNames==null || componentNames.isEmpty()) {
// Nothing to do
return;
}
for (String compName : componentNames) {
releaseComponent(compName, waitForCompsRelease);
}
}
public void logInfo()
{
int nElem = compName2Comp.size();
String message = "Components in the list: " + nElem + " (";
List<String> compNames = new ArrayList<String>(compName2Comp.keySet()); // to avoid ConcurrentModificationException
for (String compName : compNames) {
message = message + compName + " ";
}
logger.info(message + ")");
}
/**
* Convenience method for subclasses created by unit tests,
* that override {@link #getComponentFromContainerServices(String, Class)}
* and need to create and initialize component implementation classes locally inside the test.
*/
protected <T, U extends ACSComponentOperations & ComponentLifecycle>
T initCompImpl(U compImpl, Class<T> idlOpInterface)
throws ComponentLifecycleException {
compImpl.initialize(contSrv);
compImpl.execute();
return idlOpInterface.cast(compImpl);
}
}