/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.core.pc.inventory;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.hash.THashSet;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.clientapi.agent.PluginContainerException;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.domain.drift.DriftDefinition;
import org.rhq.core.domain.measurement.Availability;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.pc.PluginContainerConfiguration;
import org.rhq.core.pc.component.ComponentInvocationContextImpl;
import org.rhq.core.pc.component.ComponentInvocationContextImpl.LocalContext;
import org.rhq.core.pc.util.FacetLockType;
import org.rhq.core.pc.util.LoggingThreadFactory;
import org.rhq.core.pluginapi.availability.AvailabilityFacet;
import org.rhq.core.pluginapi.inventory.ResourceComponent;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.util.exception.ThrowableUtil;
/**
* This object holds information relative to the running state of a {@link ResourceComponent} in the Plugin Container.
* It is serializable for persistence to the Plugin Container's storage mechanisms.
*
* @author Greg Hinkle
* @author John Mazzitelli
* @author Ian Springer
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ResourceContainer implements Serializable {
private static final long serialVersionUID = 2L;
public enum SynchronizationState {
NEW, SYNCHRONIZED, DELETED_ON_AGENT, DELETED_ON_SERVER
}
public enum ResourceComponentState {
STARTED, STOPPED, STARTING
}
// thread pools used to invoke methods on container's components
private static final String DAEMON_THREAD_POOL_NAME = "ResourceContainer.invoker.daemon";
private static final String NON_DAEMON_THREAD_POOL_NAME = "ResourceContainer.invoker.nonDaemon";
private static final String AVAIL_CHECK_THREAD_POOL_NAME = "ResourceContainer.invoker.availCheck.daemon";
private static ExecutorService DAEMON_THREAD_POOL;
private static ExecutorService NON_DAEMON_THREAD_POOL;
/**
* This thread pool protects us from generating a potentially huge number of threads on slow running
* agents where avail checks are taking longer that 1s (given a default setting). Each avail check
* requests a thread on the assumption that most if not all checks will be sub-second. But if that
* is not the case we could, if using an CachedThreadPool, end up with N concurrent avail check threads,
* where N is the number of resources managed by the agent (because that type of pool can grow unbounded).
* Instead, limit the max # of threads and fall back to synchronous checking when overloaded.
*/
private static ExecutorService AVAIL_CHECK_THREAD_POOL;
// non-transient fields
private final Resource resource;
private SynchronizationState synchronizationState = SynchronizationState.NEW;
private Set<MeasurementScheduleRequest> measurementSchedule;
private Set<ResourcePackageDetails> installedPackages;
private Map<String, DriftDefinition> driftDefinitions;
private MeasurementScheduleRequest availabilitySchedule = null;
// transient fields
private transient ResourceComponent resourceComponent;
private transient ResourceContext resourceContext;
private transient ResourceComponentState resourceComponentState = ResourceComponentState.STOPPED;
private transient ReentrantReadWriteLock facetAccessLock = new ReentrantReadWriteLock();
private transient TIntObjectMap<Object> proxyCache = new TIntObjectHashMap<Object>(5);
private transient ClassLoader resourceClassLoader;
// the currently known availability
private transient AvailabilityType currentAvailType = AvailabilityType.UNKNOWN;
private transient long currentAvailStart;
// the time at which this resource is up for an avail check. 0 indicates unscheduled.
private transient long availabilityScheduleTime;
private transient AvailabilityProxy availabilityProxy;
/**
* Initialize the ResourceContainer's internals, such as its thread pools.
*
* @param pcConfig the plugin container's configuration
*/
public static void initialize(PluginContainerConfiguration pcConfig) {
LoggingThreadFactory daemonFactory = new LoggingThreadFactory(DAEMON_THREAD_POOL_NAME, true);
LoggingThreadFactory nonDaemonFactory = new LoggingThreadFactory(NON_DAEMON_THREAD_POOL_NAME, false);
LoggingThreadFactory availCheckFactory = new LoggingThreadFactory(AVAIL_CHECK_THREAD_POOL_NAME, true);
DAEMON_THREAD_POOL = Executors.newCachedThreadPool(daemonFactory);
NON_DAEMON_THREAD_POOL = Executors.newCachedThreadPool(nonDaemonFactory);
AVAIL_CHECK_THREAD_POOL = Executors.newFixedThreadPool(pcConfig.getAvailabilityScanThreadPoolSize(),
availCheckFactory);
}
/**
* Shuts down ResourceContainer's internals, such as its thread pools.
*/
public static void shutdown() {
// TODO (ips, 04/30/12): Should we funnel these through PluginContainer.shutdownExecutorService()?
DAEMON_THREAD_POOL.shutdown();
NON_DAEMON_THREAD_POOL.shutdown();
AVAIL_CHECK_THREAD_POOL.shutdown();
}
public ResourceContainer(Resource resource, ClassLoader resourceClassLoader) {
this.resource = resource;
this.resourceClassLoader = resourceClassLoader;
}
public Availability updateAvailability(AvailabilityType availabilityType) {
synchronized (this) {
this.currentAvailType = availabilityType;
this.currentAvailStart = System.currentTimeMillis();
Availability tmp = new Availability(this.resource, availabilityType);
tmp.setStartTime(this.currentAvailStart);
return tmp;
}
}
public Resource getResource() {
return this.resource;
}
/**
* Returns the currently known availability of the resource. This will return <code>null</code> if this resource is
* new and we do not yet know what its availability is.
*
* @return resource's availability or <code>null</code> if it is not known
*/
@Nullable
public Availability getAvailability() {
synchronized (this) {
Availability tmp = new Availability(this.resource, this.currentAvailType);
tmp.setStartTime(this.currentAvailStart);
return tmp;
}
}
/**
* If a piece of code wants to make a call into a plugin component's facet, and that call doesn't need to write or
* modify any data within the component or the managed resource itself, that code should obtain the returned read
* lock.
*
* @return lock that provides read-only access into all facets of this container's component.
*/
public Lock getReadFacetLock() {
return this.facetAccessLock.readLock();
}
/**
* If a piece of code wants to make a call into a plugin component's facet, and that call may need to write or
* modify data within the component or the managed resource itself, that code should obtain the returned write lock.
*
* @return lock that provides read-write access into all facets of this container's component.
*/
public Lock getWriteFacetLock() {
return this.facetAccessLock.writeLock();
}
public Set<ResourcePackageDetails> getInstalledPackages() {
synchronized (this) {
if (this.installedPackages == null) {
return Collections.emptySet();
}
return this.installedPackages;
}
}
public void setInstalledPackages(Set<ResourcePackageDetails> installedPackages) {
synchronized (this) {
this.installedPackages = installedPackages;
}
}
public ResourceComponent getResourceComponent() {
synchronized (this) {
return this.resourceComponent;
}
}
public void setResourceComponent(ResourceComponent resourceComponent) {
synchronized (this) {
this.resourceComponent = resourceComponent;
this.availabilityProxy = new AvailabilityProxy(this); // now that we have a component, we can collect its avail via a proxy
}
}
public ResourceContext getResourceContext() {
synchronized (this) {
return this.resourceContext;
}
}
public void setResourceContext(ResourceContext resourceContext) {
synchronized (this) {
this.resourceContext = resourceContext;
}
}
public Set<MeasurementScheduleRequest> getMeasurementSchedule() {
synchronized (this) {
if (this.measurementSchedule == null) {
return Collections.emptySet();
} else {
return this.measurementSchedule;
}
}
}
public void setMeasurementSchedule(Set<MeasurementScheduleRequest> measurementSchedule) {
synchronized (this) {
this.measurementSchedule = new THashSet<MeasurementScheduleRequest>(measurementSchedule);
// this should not happen but if it does, protect against it because it will sink the agent
if (null != this.measurementSchedule) {
for (MeasurementScheduleRequest sched : this.measurementSchedule) {
if (sched.getInterval() < MeasurementSchedule.MINIMUM_INTERVAL) {
String smallStack = ThrowableUtil.getFilteredStackAsString(new Throwable());
String msg = "Invalid collection interval ["
+ sched
+ "] for Resource ["
+ resource
+ "]. Setting it to 20 minutes until the situation is corrected. Please report to Development: "
+ smallStack;
LogFactory.getLog(ResourceContainer.class).error(msg);
sched.setInterval(20L * 60L * 1000L);
}
}
}
}
}
public MeasurementScheduleRequest getAvailabilitySchedule() {
// platforms don't have a schedule but other types should. If one has not yet been set (this can
// happen in various upgrade scenarios) set one, using a default interval.
synchronized (this) {
if (null == availabilitySchedule) {
switch (this.resource.getResourceType().getCategory()) {
case PLATFORM:
break;
case SERVER:
availabilitySchedule = new MeasurementScheduleRequest(-1, MeasurementDefinition.AVAILABILITY_NAME,
MeasurementDefinition.AVAILABILITY_DEFAULT_PERIOD_SERVER, true, DataType.AVAILABILITY);
break;
case SERVICE:
availabilitySchedule = new MeasurementScheduleRequest(-1, MeasurementDefinition.AVAILABILITY_NAME,
MeasurementDefinition.AVAILABILITY_DEFAULT_PERIOD_SERVICE, true, DataType.AVAILABILITY);
break;
}
}
}
return availabilitySchedule;
}
public void setAvailabilitySchedule(MeasurementScheduleRequest availabilitySchedule) {
synchronized (this) {
this.availabilitySchedule = availabilitySchedule;
// when the schedule is (re)set just 0 out the schedule time and it will get rescheduled on the
// next avail execution.
this.availabilityScheduleTime = 0;
}
}
public long getAvailabilityScheduleTime() {
return availabilityScheduleTime;
}
/**
* Sets the availability schedule time; synchronized to ensure visibility.
*/
public synchronized void setAvailabilityScheduleTime(long availabilityScheduleTime) {
this.availabilityScheduleTime = availabilityScheduleTime;
}
/**
* Submits a task to perform an availability check asynchronously.
* NOTE: this is package scoped so the avail proxy can call it and submit itself as a task to the containers thread pool.
*
* @return the future that will provide the avail value
*/
Future<AvailabilityType> submitAvailabilityCheck(Callable<AvailabilityType> callable) {
return AVAIL_CHECK_THREAD_POOL.submit(callable);
}
/**
* Updates the measurementSchedule with the modifications made in the measurementScheduleUpdate.
*
* @param measurementScheduleUpdate the updates to the current measurementSchedule
*
* @return true if the schedule was updated successfully, false otherwise or if measurementScheduleUpdate is null
*/
public boolean updateMeasurementSchedule(Set<MeasurementScheduleRequest> measurementScheduleUpdate) {
if (null == measurementScheduleUpdate || measurementScheduleUpdate.size() == 0) {
return false;
}
// this should not happen but if it does, protect against it because it will sink the agent
for (MeasurementScheduleRequest sched : measurementScheduleUpdate) {
if (sched.getInterval() < MeasurementSchedule.MINIMUM_INTERVAL) {
String smallStack = ThrowableUtil.getFilteredStackAsString(new Throwable());
String msg = "Invalid collection interval [" + sched + "] for Resource [" + resource
+ "]. Setting it to 20 minutes until the situation is corrected. Please report to Development: "
+ smallStack;
LogFactory.getLog(ResourceContainer.class).error(msg);
sched.setInterval(20L * 60L * 1000L);
}
}
Set<Integer> updateScheduleIds = new HashSet<Integer>();
for (MeasurementScheduleRequest update : measurementScheduleUpdate) {
updateScheduleIds.add(update.getScheduleId());
}
synchronized (this) {
if (this.measurementSchedule == null) {
this.measurementSchedule = new HashSet<MeasurementScheduleRequest>(measurementScheduleUpdate.size());
}
Set<MeasurementScheduleRequest> toBeRemoved = new HashSet<MeasurementScheduleRequest>();
for (MeasurementScheduleRequest current : this.measurementSchedule) {
if (updateScheduleIds.contains(current.getScheduleId())) {
toBeRemoved.add(current);
}
}
// first remove all the old versions of the measurement schedules
this.measurementSchedule.removeAll(toBeRemoved);
// then add the new versions
return this.measurementSchedule.addAll(measurementScheduleUpdate);
}
}
public Collection<DriftDefinition> getDriftDefinitions() {
synchronized (this) {
if (driftDefinitions == null) {
return Collections.emptyList();
}
return driftDefinitions.values();
}
}
public boolean containsDriftDefinition(DriftDefinition d) {
synchronized (this) {
if (driftDefinitions == null)
return false;
return driftDefinitions.containsKey(d.getName());
}
}
public void addDriftDefinition(DriftDefinition d) {
synchronized (this) {
if (driftDefinitions == null) {
driftDefinitions = new HashMap<String, DriftDefinition>(1);
}
driftDefinitions.put(d.getName(), d);
}
}
public void removeDriftDefinition(DriftDefinition d) {
synchronized (this) {
if (driftDefinitions != null) {
driftDefinitions.remove(d.getName());
if (driftDefinitions.isEmpty()) {
driftDefinitions = null;
}
}
}
}
public ResourceComponentState getResourceComponentState() {
synchronized (this) {
return this.resourceComponentState;
}
}
public void setResourceComponentState(ResourceComponentState state) {
synchronized (this) {
this.resourceComponentState = state;
}
}
public SynchronizationState getSynchronizationState() {
synchronized (this) {
return this.synchronizationState;
}
}
public void setSynchronizationState(SynchronizationState synchronizationState) {
synchronized (this) {
this.synchronizationState = synchronizationState;
}
}
public ClassLoader getResourceClassLoader() {
return this.resourceClassLoader;
}
/**
* Sets the classloader that should be used by the resource when its component interfaces are being invoked.
* In most (but not all) cases, this is the plugin classloader of the plugin that defined the resource.
*
* @param resourceClassLoader the resource's context classloader
*/
public void setResourceClassLoader(ClassLoader resourceClassLoader) {
this.resourceClassLoader = resourceClassLoader;
}
@Override
public String toString() {
AvailabilityType avail = (this.currentAvailType != null) ? this.currentAvailType : null;
return this.getClass().getSimpleName() + "[resource=" + this.resource + ", syncState="
+ this.synchronizationState + ", componentState=" + this.resourceComponentState + ", avail=" + avail + "]";
}
/**
* Creates a proxy to this container's resource component, essentially returning the component exposed as the given
* facet interface. This proxy will ensure that calls to the component's interface are synchronized with the given
* lock type. If <code>lockType</code> is {@link FacetLockType#NONE} and there is no timeout, then the resource's
* actual component instance is returned as-is (i.e. it will not be wrapped in a proxy - which means this returns
* the same as {@link #getResourceComponent()}).
*
* @param facetInterface the interface that the component implements and will expose via the proxy
* @param lockType the type of lock to use when synchronizing access; must not be null
* @param timeout if the method invocation thread has not completed after this many seconds
* (in milliseconds), interrupt it; value must be positive and is rounded up to nearest second.
* @param daemonThread whether or not the thread used for the invocation should be a daemon thread
* @param onlyIfStarted if <code>true</code>, and the component is not started, an exception is thrown
* @param transferInterrupt whether or not interruption of the calling thread should be transfered to the executor
* thread
*
* @return a proxy that wraps the given component and exposes the given facet interface; will never be null
*
* @throws PluginContainerException if the component does not exist or does not implement the interface
*/
public <T> T createResourceComponentProxy(Class<T> facetInterface, FacetLockType lockType, long timeout,
boolean daemonThread, boolean onlyIfStarted, boolean transferInterrupt) throws PluginContainerException {
if (onlyIfStarted) {
if (!ResourceComponentState.STARTED.equals(getResourceComponentState())) {
throw new PluginContainerException("Resource component could not be retrieved for resource ["
+ getResource() + "] because the component is not started. Its state is ["
+ getResourceComponentState() + "]");
}
}
ResourceComponent resourceComponent = this.getResourceComponent();
if (resourceComponent == null) {
throw new PluginContainerException("Component does not exist for resource: " + getResource());
}
if (!(facetInterface.isAssignableFrom(resourceComponent.getClass()))) {
throw new PluginContainerException("Component does not support the [" + facetInterface.getName()
+ "] interface: " + this);
}
// If no locking is required and there is no timeout, there is no need for a proxy - return the actual component.
if (lockType == FacetLockType.NONE && timeout == 0) {
return (T) resourceComponent;
}
// Check for a cached proxy.
int key;
key = facetInterface.hashCode();
key = 31 * key + lockType.hashCode();
key = 31 * key + (int) (timeout ^ (timeout >>> 32));
key = 31 * key + (daemonThread ? 1 : 0);
key = 31 * key + (transferInterrupt ? 1 : 0);
synchronized (this) {
if (this.proxyCache == null) {
this.proxyCache = new TIntObjectHashMap<Object>(5);
}
T proxy = (T) this.proxyCache.get(key);
if (proxy == null) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// this is the handler that will actually acquire the lock and invoke the facet method call
ResourceComponentInvocationHandler handler = new ResourceComponentInvocationHandler(this, lockType,
timeout, daemonThread, facetInterface, transferInterrupt);
// this is the proxy that will look like the facet interface that the caller will use
proxy = (T) Proxy.newProxyInstance(classLoader, new Class<?>[] { facetInterface }, handler);
this.proxyCache.put(key, proxy);
}
return proxy;
}
}
public boolean supportsFacet(Class facetInterface) {
ResourceComponent thisComponent = this.getResourceComponent();
if (thisComponent == null) {
return false;
}
return facetInterface.isAssignableFrom(thisComponent.getClass());
}
private String getFacetLockStatus() {
StringBuilder str = new StringBuilder("Facet lock status for [");
str.append(getResource());
str.append("], is-write-locked=[").append(facetAccessLock.isWriteLocked());
str.append("], is-write-locked-by-current-thread=[").append(facetAccessLock.isWriteLockedByCurrentThread());
str.append("], read-locks=[").append(facetAccessLock.getReadLockCount());
str.append("], waiting-for-lock-queue-size=[").append(facetAccessLock.getQueueLength());
str.append("]");
return str.toString();
}
// Recreate the facet lock on deserialization.
private Object readResolve() throws java.io.ObjectStreamException {
this.facetAccessLock = new ReentrantReadWriteLock();
return this;
}
/**
* Return a proxy for a call to check resource availability, using the daemon thread pool.
*
* @see AvailabilityProxy for details
*/
public AvailabilityFacet getAvailabilityProxy() {
return this.availabilityProxy;
}
/**
* This is a ResourceComponent proxy that invokes component methods in pooled threads. Depending on the parameters
* passed to its constructor, it may also:
*
* 1) obtain a facet lock before passing the invocation call to the actual component, and/or
* 2) interrupt the invocation thread and throw a {@link TimeoutException} if its execution time exceeds a
* specified timeout
*/
private static class ResourceComponentInvocationHandler implements InvocationHandler {
private static final Log LOG = LogFactory.getLog(ResourceComponentInvocationHandler.class);
private final ResourceContainer container;
private final Lock lock;
private final int timeoutInSeconds;
private final boolean daemonThread;
private final Class facetInterface;
private final boolean transferInterrupt;
/**
*
* @param container the resource container managing the resource component upon which the method will be invoked;
* caller must ensure the container's component is never null
* @param lockType the type of facet lock to acquire for the invocation; must not be null
* @param timeout if the method invocation thread has not completed after this many milliseconds, interrupt it;
* value must be positive
* @param daemonThread whether or not the thread used for the invocation should be a daemon thread
* @param facetInterface the interface that the component implements that is being exposed by this proxy
* @param transferInterrupt whether or not interruption of the calling thread should be transfered to the
* executor thread
*/
public ResourceComponentInvocationHandler(ResourceContainer container, FacetLockType lockType, long timeout,
boolean daemonThread, Class facetInterface, boolean transferInterrupt) {
this.container = container;
switch (lockType) {
case WRITE: {
this.lock = container.getWriteFacetLock();
break;
}
case READ: {
this.lock = container.getReadFacetLock();
break;
}
default: {
this.lock = null;
break;
}
}
if (timeout <= 0L) {
throw new IllegalArgumentException("timeout value is not positive.");
}
this.timeoutInSeconds = (int) ((timeout + 999L) / 1000L); // round up, ensure 1sec minimum.
this.daemonThread = daemonThread;
this.facetInterface = facetInterface;
this.transferInterrupt = transferInterrupt;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass().equals(this.facetInterface)) {
return invokeInNewThreadWithLock(method, args);
} else {
// toString(), etc.
return invokeInCurrentThreadWithoutLock(method, args);
}
}
private Object invokeInNewThreadWithLock(Method method, Object[] args) throws Throwable {
ExecutorService threadPool = this.daemonThread ? DAEMON_THREAD_POOL : NON_DAEMON_THREAD_POOL;
ComponentInvocation componentInvocation = new ComponentInvocation(this.container, method, args, this.lock);
Future<?> future = threadPool.submit(componentInvocation);
try {
return future.get(this.timeoutInSeconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.error("Thread [" + Thread.currentThread().getName() + "] was interrupted.");
if (this.transferInterrupt) {
future.cancel(true);
componentInvocation.markContextInterrupted();
}
throw new RuntimeException(invokedMethodString(method, args, "was rudely interrupted."), e);
} catch (ExecutionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug(invokedMethodString(method, args, "failed."), e);
}
throw e.getCause();
} catch (java.util.concurrent.TimeoutException e) {
String msg = invokedMethodString(method, args, "timed out after " + timeoutInSeconds
+ " seconds - invocation thread will be interrupted.");
LOG.debug(msg);
Throwable cause = new Throwable();
cause.setStackTrace(componentInvocation.getStackTrace());
future.cancel(true);
componentInvocation.markContextInterrupted();
if (LOG.isDebugEnabled()) {
LOG.debug(this.container.getFacetLockStatus());
}
throw new TimeoutException(msg).initCause(cause);
}
}
private String invokedMethodString(Method method, Object[] methodArgs, String extraMsg) {
String name = this.container.getResourceComponent().getClass().getName() + '.' + method.getName() + "()";
String args = ((methodArgs != null) ? Arrays.asList(methodArgs).toString() : "");
return "Call to [" + name + "] with args [" + args + "] " + extraMsg;
}
private Object invokeInCurrentThreadWithoutLock(Method method, Object[] args) throws Throwable {
Thread thread = Thread.currentThread();
ClassLoader originalContextClassLoader = thread.getContextClassLoader();
try {
ClassLoader pluginClassLoader = this.container.getResourceClassLoader();
if (pluginClassLoader == null) {
throw new IllegalStateException("No plugin classloader was specified for " + this + ".");
}
thread.setContextClassLoader(pluginClassLoader);
// This is the actual call into the resource component.
return method.invoke(this.container.getResourceComponent(), args);
} catch (InvocationTargetException ite) {
throw (ite.getCause() != null) ? ite.getCause() : ite;
} finally {
thread.setContextClassLoader(originalContextClassLoader);
}
}
}
private static class ComponentInvocation implements Callable {
private static final Log LOG = LogFactory.getLog(ComponentInvocation.class);
private final ResourceContainer resourceContainer;
private final Method method;
private final Object[] args;
private final Lock lock;
private final ComponentInvocationContextImpl componentInvocationContext;
private final LocalContext localContext;
private volatile Thread thread;
ComponentInvocation(ResourceContainer resourceContainer, Method method, Object[] args, Lock lock) {
this.resourceContainer = resourceContainer;
this.method = method;
this.args = args;
this.lock = lock;
this.componentInvocationContext = (ComponentInvocationContextImpl) resourceContainer.getResourceContext()
.getComponentInvocationContext();
localContext = new LocalContext();
}
/**
* Return the stack trace for the thread executing this call.
* Returns an empty stack trace if not called.
*/
public StackTraceElement[] getStackTrace() throws Exception {
if (thread == null)
return new StackTraceElement[0];
return thread.getStackTrace();
}
public Object call() throws Exception {
this.thread = Thread.currentThread();
if (this.lock != null) {
try {
this.lock.lockInterruptibly();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
// If we made it here, we have acquired the lock.
}
componentInvocationContext.setLocalContext(localContext);
ClassLoader originalContextClassLoader = thread.getContextClassLoader();
// The thread needs to run with a fresh invocation context
try {
ClassLoader pluginClassLoader = this.resourceContainer.getResourceClassLoader();
if (pluginClassLoader == null) {
throw new IllegalStateException("No plugin class loader was specified for " + this + ".");
}
thread.setContextClassLoader(pluginClassLoader);
// This is the actual call into the resource component's facet interface.
ResourceComponent resourceComponent = this.resourceContainer.getResourceComponent();
return this.method.invoke(resourceComponent, this.args);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
//noinspection ThrowableInstanceNeverThrown
throw (cause instanceof Exception) ? (Exception) cause : new Exception(cause);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (this.lock != null) {
this.lock.unlock();
}
this.thread.setContextClassLoader(originalContextClassLoader);
this.thread = null;
}
}
public void markContextInterrupted() {
localContext.markInterrupted();
LOG.warn(getContextInterruptedWarningMessage(LOG.isDebugEnabled()));
}
private String getContextInterruptedWarningMessage(boolean detailed) {
StringBuilder sb = new StringBuilder();
sb.append("Invocation has been marked interrupted for method [");
if (detailed) {
sb.append(method.toGenericString());
} else {
sb.append(method.getDeclaringClass().getSimpleName()).append(".").append(method.getName());
}
sb.append("] on resource [").append(resourceContainer.getResource()).append("]");
return sb.toString();
}
}
}