/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dependency.plugins;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.jboss.dependency.plugins.action.ControllerContextAction;
import org.jboss.dependency.plugins.action.SimpleControllerContextAction;
import org.jboss.dependency.plugins.tracker.AbstractContextRegistry;
import org.jboss.dependency.spi.CallbackItem;
import org.jboss.dependency.spi.Controller;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.dependency.spi.ControllerContextActions;
import org.jboss.dependency.spi.ControllerMode;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.dependency.spi.ControllerStateModel;
import org.jboss.dependency.spi.DependencyInfo;
import org.jboss.dependency.spi.DependencyItem;
import org.jboss.dependency.spi.LifecycleCallbackItem;
import org.jboss.dependency.spi.asynchronous.AsynchronousController;
import org.jboss.dependency.spi.graph.GraphController;
import org.jboss.dependency.spi.graph.LookupStrategy;
import org.jboss.dependency.spi.graph.SearchInfo;
import org.jboss.dependency.spi.tracker.ContextFilter;
import org.jboss.dependency.spi.tracker.ContextQueries;
import org.jboss.dependency.spi.tracker.ContextRegistry;
import org.jboss.util.JBossObject;
import org.jboss.util.collection.CollectionsFactory;
/**
* Abstract controller.
*
* @author <a href="adrian@jboss.com">Adrian Brock</a>
* @author <a href="ales.justin@jboss.com">Ales Justin</a>
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
* @version $Revision: 97439 $
*/
public class AbstractController extends JBossObject implements Controller, GraphController, AbstractControllerMBean, AsynchronousController, ContextQueries, ContextRegistry
{
/** The lock */
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/**
* The executor used to install ASYNCHRONOUS contexts. It must have a saturation policy of
* (or semantically similar to) ThreadPoolExecutor.AbortPolicy or ThreadPoolExecutor.CallerRunsPolicy
*/
private Executor executor;
/** Whether we are shutdown */
private boolean shutdown = false;
/** The controller state model */
private AbstractControllerStateModel stateModel = new MapControllerStateModel();
/** All contexts by name Map<Object, ControllerContext> */
private Map<Object, ControllerContext> allContexts = new ConcurrentHashMap<Object, ControllerContext>();
/** The contexts by state Map<ControllerState, Set<ControllerContext>> */
private Map<ControllerState, Set<ControllerContext>> contextsByState = new ConcurrentHashMap<ControllerState, Set<ControllerContext>>();
/** The error contexts Map<Name, ControllerContext> */
private Map<Object, ControllerContext> errorContexts = new ConcurrentHashMap<Object, ControllerContext>();
/** The contexts that are currently being resolved/installed */
private Set<ControllerContext> installing = new CopyOnWriteArraySet<ControllerContext>();
/** The contexts that are currently being installed by the executor */
ContextsInstalledByExecutor contextsInstalledByExecutor = new ContextsInstalledByExecutor();
/** The parent controller */
private AbstractController parentController;
/** The child controllers */
private Set<AbstractController> childControllers = new CopyOnWriteArraySet<AbstractController>();
/** The callback items */
private Map<Object, Set<CallbackItem<?>>> installCallbacks = new ConcurrentHashMap<Object, Set<CallbackItem<?>>>();
private Map<Object, Set<CallbackItem<?>>> uninstallCallbacks = new ConcurrentHashMap<Object, Set<CallbackItem<?>>>();
/** Whether an on demand context has been enabled */
private boolean onDemandEnabled = true;
/** Whether stats are enabled */
private boolean collectStats = false;
/** The install stats */
private volatile StateStatistics installStats = null;
/** The context tracker */
private AbstractContextRegistry registry;
/**
* Create an abstract controller
*/
public AbstractController()
{
addState(ControllerState.NOT_INSTALLED, null);
addState(ControllerState.PRE_INSTALL, null);
addState(ControllerState.DESCRIBED, null);
addState(ControllerState.INSTANTIATED, null);
addState(ControllerState.CONFIGURED, null);
addState(ControllerState.CREATE, null);
addState(ControllerState.START, null);
addState(ControllerState.INSTALLED, null);
registry = createContextRegistry();
}
/**
* Create context registry.
*
* @return the context registry
*/
protected AbstractContextRegistry createContextRegistry()
{
return new AbstractContextRegistry(this);
}
/**
* Set the executor used to install ASYNCHRONOUS contexts. It must have a saturation policy of
* (or semantically similar to) ThreadPoolExecutor.AbortPolicy or ThreadPoolExecutor.CallerRunsPolicy.
*
* @param executor the executor
*/
public void setExecutor(Executor executor)
{
// TODO - security
lockWrite();
try
{
this.executor = executor;
}
finally
{
unlockWrite();
}
}
/**
* Get the executor.
*
* @return the executor
*/
public Executor getExecutor()
{
// TODO - security
return getExecutionEnvironment();
}
/**
* Get the executor internal w/o security check.
*
* @return the executor
*/
protected Executor getExecutionEnvironment()
{
lockRead();
try
{
return executor;
}
finally
{
unlockRead();
}
}
public boolean isShutdown()
{
lockWrite();
try
{
return shutdown;
}
finally
{
unlockWrite();
}
}
/**
* Check whether the controller is shutdown
*
* @throws IllegalStateException when already shutdown
*/
public void checkShutdown()
{
lockWrite();
try
{
if (shutdown)
throw new IllegalStateException("Already shutdown");
}
finally
{
unlockWrite();
}
}
public void shutdown()
{
lockWrite();
try
{
Set<AbstractController> children = getControllers();
if (children != null && children.isEmpty() == false)
{
for (AbstractController child : children)
{
try
{
child.shutdown();
}
catch (Throwable t)
{
log.warn("Error during shutdown of child: " + child, t);
}
}
}
Set<ControllerContext> contexts = getAllContexts();
if (contexts != null && contexts.isEmpty() == false)
{
for (ControllerContext context : contexts)
{
try
{
uninstall(context.getName());
}
catch (Throwable t)
{
log.warn("Error during shutdown while uninstalling: " + context, t);
}
}
}
}
finally
{
shutdown = true;
unlockWrite();
}
}
/**
* Get the collectStats.
*
* @return the collectStats.
*/
public boolean isCollectStats()
{
return collectStats;
}
/**
* Set the collectStats.
*
* @param collectStats the collectStats.
*/
public void setCollectStats(boolean collectStats)
{
this.collectStats = collectStats;
}
public String listStateTimes(boolean details)
{
synchronized (this)
{
if (installStats == null)
return "No statistics available";
return installStats.listTimes(details);
}
}
public void addState(ControllerState state, ControllerState before)
{
lockWrite();
try
{
if (stateModel.addState(state, before))
{
Set<ControllerContext> contexts = new CopyOnWriteArraySet<ControllerContext>();
contextsByState.put(state, contexts);
}
}
finally
{
unlockWrite();
}
}
/**
* Add controller context.
*
* This is normally used when switching from top
* level controller to a scoped one.
*
* @param context the controller context
*/
void addControllerContext(ControllerContext context)
{
lockWrite();
try
{
registerControllerContext(context);
}
finally
{
unlockWrite();
}
}
/**
* Remove controller context.
*
* This is normally used when switching from scoped
* level controller to a top one.
*
* @param context the controller context
*/
void removeControllerContext(ControllerContext context)
{
lockWrite();
try
{
unregisterControllerContext(context);
}
finally
{
unlockWrite();
}
}
/**
* Get the parent controller.
*
* @return the parent controller
*/
public AbstractController getParentController()
{
return parentController;
}
/**
* Set the parent controller.
*
* @param parentController the parent controller
*/
protected void setParentController(AbstractController parentController)
{
this.parentController = parentController;
}
/**
* Get child controllers.
*
* @return the child controllers
*/
public Set<AbstractController> getControllers()
{
lockRead();
try
{
return Collections.unmodifiableSet(childControllers);
}
finally
{
unlockRead();
}
}
/**
* Add child controller.
*
* @param controller the child controller
* @return true if equal controller has been already added, see Set.add usage
*/
public boolean addController(AbstractController controller)
{
lockWrite();
try
{
return childControllers.add(controller);
}
finally
{
unlockWrite();
}
}
/**
* Remove child controller.
*
* @param controller the child controller
* @return true if equal controller was present, see Set.remove usage
*/
public boolean removeController(AbstractController controller)
{
lockWrite();
try
{
return childControllers.remove(controller);
}
finally
{
unlockWrite();
}
}
/**
* Whether the controller has contexts
*
* @return true when there are registered contexts
*/
public boolean isActive()
{
lockRead();
try
{
// is this active
if (allContexts.isEmpty() == false)
return true;
// any of the children still active
for (AbstractController child : getControllers())
{
if (child.isActive())
return true;
}
return false;
}
finally
{
unlockRead();
}
}
public ControllerContext getContext(Object name, ControllerState state, SearchInfo info)
{
if (info == null)
throw new IllegalArgumentException("Null search info.");
LookupStrategy strategy = info.getStrategy();
if (strategy == null)
throw new IllegalArgumentException("AbstractController doesn't implement this search info: " + info);
if (log.isTraceEnabled())
log.trace("Executing search " + info.getType());
return strategy.getContext(this, name, state);
}
/**
* Get all the contexts.
* In state decending order.
*
* @return all contexts
*/
public Set<ControllerContext> getAllContexts()
{
lockRead();
try
{
LinkedHashSet<ControllerContext> result = new LinkedHashSet<ControllerContext>();
ListIterator<ControllerState> it = stateModel.listIteraror();
if (it.hasNext())
{
result.addAll(getContextsByState(it.next()));
while (it.hasPrevious())
{
result.addAll(getContextsByState(it.previous()));
}
}
result.addAll(errorContexts.values());
return result;
}
finally
{
unlockRead();
}
}
public ControllerContext getContext(Object name, ControllerState state)
{
if (name == null)
throw new IllegalArgumentException("Null name");
lockRead();
try
{
ControllerContext result = getRegisteredControllerContext(name, false);
if (result == null && name instanceof Class) // check type matching
result = getContextByClass((Class<?>)name);
if (result != null && state != null && stateModel.isBeforeState(result.getState(), state))
{
return null;
}
return result;
}
finally
{
unlockRead();
}
}
public ControllerContext getInstalledContext(Object name)
{
return getContext(name, ControllerState.INSTALLED);
}
public Set<ControllerContext> getNotInstalled()
{
lockWrite();
try
{
Set<ControllerContext> result = new HashSet<ControllerContext>(errorContexts.values());
for (ControllerState state : stateModel)
{
if (ControllerState.INSTALLED.equals(state))
break;
Set<ControllerContext> stateContexts = getContextsByState(state);
result.addAll(stateContexts);
}
return result;
}
finally
{
unlockWrite();
}
}
public ControllerStateModel getStates()
{
return stateModel;
}
public Set<ControllerContext> getContextsByState(ControllerState state)
{
return contextsByState.get(state);
}
public void install(ControllerContext context) throws Throwable
{
boolean trace = log.isTraceEnabled();
if (context == null)
throw new IllegalArgumentException("Null context");
Object name = context.getName();
if (name == null)
throw new IllegalArgumentException("Null name " + context.toShortString());
install(context, trace);
}
public void change(ControllerContext context, ControllerState state) throws Throwable
{
boolean trace = log.isTraceEnabled();
if (context == null)
throw new IllegalArgumentException("Null context");
if (state == null)
throw new IllegalArgumentException("Null state");
change(context, state, trace);
}
public void enableOnDemand(ControllerContext context) throws Throwable
{
boolean trace = log.isTraceEnabled();
if (context == null)
throw new IllegalArgumentException("Null context");
enableOnDemand(context, trace);
}
public ControllerContext uninstall(Object name)
{
return uninstall(name, 0);
}
/**
* Get controller id - impl detail.
* It should be unique.
*
* @return controller's id
*/
protected String getId()
{
StringBuffer buffer = new StringBuffer();
buffer.append(getClass().getSimpleName());
buffer.append("[").append(System.identityHashCode(this)).append("]");
return buffer.toString();
}
public void addAlias(Object alias, Object original) throws Throwable
{
Map<ControllerState, ControllerContextAction> map = createAliasActions();
ControllerContextActions actions = new AbstractControllerContextActions(map);
AliasControllerContext context = new InnerAliasControllerContext(alias, getId(), original, actions);
preAliasInstall(context);
install(context);
// is alias in error
Throwable error = context.getError();
if (error != null)
throw error;
if (ControllerState.ERROR.equals(context.getState()))
throw new IllegalArgumentException("Alias " + context + " is in error.");
}
/**
* Create alias controller context actions.
*
* @return the alias controller context actions
*/
protected Map<ControllerState, ControllerContextAction> createAliasActions()
{
return Collections.<ControllerState, ControllerContextAction>singletonMap(ControllerState.INSTALLED, new AliasControllerContextAction());
}
/**
* Apply pre install - e.g. scope key in scoped controller.
*
* @param aliasContext the new alias context
*/
protected void preAliasInstall(ControllerContext aliasContext)
{
}
public void removeAlias(Object alias)
{
uninstall(alias + "_Alias_" + getId());
}
/**
* Uninstall the context.
*
* @param name the context name
* @param level the controller level
* @return uninstalled controller context
*/
// todo - some better way to find context's by name
// currently the first one found is used
protected ControllerContext uninstall(Object name, int level)
{
boolean trace = log.isTraceEnabled();
if (name == null)
throw new IllegalArgumentException("Null name");
lockWrite();
try
{
if (errorContexts.remove(name) != null && trace)
log.trace("Tidied up context in error state: " + name);
ControllerContext context = getRegisteredContextAndInterruptAsynchronousInstall(name);
if (context != null)
{
if (trace)
log.trace("Uninstalling " + context.toShortString());
// get a hold on possible parent before its nullified in uninstall
AbstractController parent = getParentController();
uninstallContext(context, stateModel.getInitialState(), trace);
try
{
unregisterControllerContext(context);
}
catch (Throwable t)
{
log.warn("Error unregistering context: " + context.toShortString() + " with name: " + name);
}
while (parent != null)
{
try
{
parent.unregisterControllerContext(context);
}
catch (Throwable t)
{
log.warn("Error unregistering context in parent controller: " + context.toShortString() + " with name: " + name);
}
parent = parent.getParentController();
}
}
else
{
for (AbstractController controller : getControllers())
{
context = controller.uninstall(name, level + 1);
if (context != null)
break;
}
}
if (context == null && level == 0)
throw new IllegalStateException("Not installed: " + name);
return context;
}
finally
{
unlockWrite();
}
}
/**
* Obtains the context, having interrupted its installation process if it is an asynchronous context
* currently being installed
* @param name The name of the context
* @return the context or null if not found
*/
private ControllerContext getRegisteredContextAndInterruptAsynchronousInstall(Object name)
{
ControllerContext context = getRegisteredControllerContext(name, false);
if (context != null)
{
contextsInstalledByExecutor.interruptTaskAndBlock(context, this);
context = getRegisteredControllerContext(name, false);
}
return context;
}
/**
* Install a context
*
* @param context the context
* @param trace whether trace is enabled
* @throws Throwable for any error
*/
protected void install(ControllerContext context, boolean trace) throws Throwable
{
lockWrite();
try
{
checkShutdown();
Object name = context.getName();
// Check the name is not already registered
if (getRegisteredControllerContext(name, false) != null)
throw new IllegalStateException(name + " is already installed.");
// Check any alias is not already registered
Set<Object> aliases = context.getAliases();
if (aliases != null && aliases.isEmpty() == false)
{
for (Object alias : aliases)
{
if (getRegisteredControllerContext(alias, false) != null)
throw new IllegalStateException(alias + " an alias of " + name + " is already installed.");
}
}
// set the required state
ControllerMode mode = context.getMode();
context.setRequiredState(mode.getRequiredState());
if (trace)
log.trace("Installing " + context.toShortString());
context.setController(this);
DependencyInfo dependencies = context.getDependencyInfo();
if (trace)
{
String dependsOn = "[]";
if (dependencies != null)
{
try
{
Set<DependencyItem> set = dependencies.getIDependOn(null);
if (set != null)
dependsOn = set.toString();
}
catch (Throwable t)
{
log.warn("Exception getting dependencies: " + t);
dependsOn = null;
}
}
if (dependsOn != null)
log.trace("Dependencies for " + name + ": " + dependsOn);
}
boolean ok = incrementState(context, trace);
if (ok)
{
try
{
registerControllerContext(context);
}
catch (Throwable t)
{
// This is probably unreachable? But let's be paranoid
ok = false;
throw t;
}
}
if (ok)
{
resolveContexts(trace);
}
else
{
errorContexts.remove(context);
throw context.getError();
}
}
finally
{
unlockWrite();
}
}
/**
* Change a context's state
*
* @param context the context
* @param state the required state
* @param trace whether trace is enabled
* @throws Throwable for any error
*/
protected void change(ControllerContext context, ControllerState state, boolean trace) throws Throwable
{
lockWrite();
try
{
checkShutdown();
if (!stateModel.isValidState(state))
throw new IllegalArgumentException("Unknown state: " + state);
if (context.getState().equals(state))
{
if (trace)
log.trace("No change required toState=" + state.getStateString() + " " + context.toShortString());
return;
}
if (trace)
log.trace("Change toState=" + state.getStateString() + " " + context.toShortString());
context.setRequiredState(state);
if (stateModel.isBeforeState(context.getState(), state))
resolveContexts(trace);
else
{
while (stateModel.isAfterState(context.getState(), state))
{
uninstallContext(context, trace);
}
}
}
finally
{
unlockWrite();
}
}
/**
* Enable an on demand context
*
* @param context the context
* @param trace whether trace is enabled
* @throws Throwable for any error
*/
protected void enableOnDemand(ControllerContext context, boolean trace) throws Throwable
{
lockWrite();
try
{
checkShutdown();
if (ControllerMode.ON_DEMAND.equals(context.getMode()) == false)
throw new IllegalStateException("Context is not ON DEMAND: " + context.toShortString());
// Sanity check
getRegisteredControllerContext(context.getName(), true);
// Already done
if (ControllerState.INSTALLED.equals(context.getRequiredState()))
return;
context.setRequiredState(ControllerState.INSTALLED);
if (trace)
log.trace("Enable onDemand: " + context.toShortString());
onDemandEnabled = true;
}
finally
{
unlockWrite();
}
}
/**
* Increment state<p>
* <p/>
* This method must be invoked with the write lock taken.
*
* @param context the context
* @param trace whether trace is enabled
* @return whether the suceeded
*/
protected boolean incrementState(ControllerContext context, boolean trace)
{
ControllerState fromState = context.getState();
Controller fromController = context.getController();
Set<ControllerContext> fromContexts;
ControllerState toState;
if (ControllerState.ERROR.equals(fromState))
{
ControllerState initialState = stateModel.getInitialState();
errorContexts.remove(context);
Throwable error = null;
unlockWrite();
try
{
install(context, ControllerState.ERROR, initialState);
}
catch (Throwable t)
{
error = t;
}
finally
{
lockWrite();
if (error != null)
{
log.error("Error during initial installation: " + context.toShortString(), error);
context.setError(error);
errorContexts.put(context.getName(), context);
return false;
}
}
Set<ControllerContext> notInstalled = fromController.getContextsByState(initialState);
notInstalled.add(context);
context.setState(initialState);
return true;
}
else
{
fromContexts = fromController.getContextsByState(fromState);
if (fromContexts.contains(context) == false)
throw new IllegalStateException("Context not found in previous state (" + fromState + "): " + context.toShortString());
toState = stateModel.getNextState(fromState);
if (toState == null)
throw new IllegalStateException("No state after " + fromState);
}
unlockWrite();
Throwable error = null;
try
{
install(context, fromState, toState);
if (fromContexts != null)
fromContexts.remove(context);
Controller toController = context.getController();
Set<ControllerContext> toContexts = toController.getContextsByState(toState);
toContexts.add(context);
context.setState(toState);
handleInstallLifecycleCallbacks(context, toState);
resolveCallbacks(context, toState, true);
}
catch (Throwable t)
{
error = t;
}
finally
{
lockWrite();
if (error != null)
{
log.error("Error installing to " + toState.getStateString() + ": " + context.toShortString(), error);
uninstallContext(context, stateModel.getInitialState(), trace);
errorContexts.put(context.getName(), context);
context.setError(error);
return false;
}
}
return true;
}
/**
* Resolve unresolved contexts<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param trace whether trace is enabled
*/
protected void resolveContexts(boolean trace)
{
boolean resolutions = true;
while (resolutions || onDemandEnabled)
{
onDemandEnabled = false;
resolutions = false;
for (ControllerState fromState : stateModel)
{
ControllerState toState = stateModel.getNextState(fromState);
if (resolveContexts(fromState, toState, trace))
{
resolutions = true;
break;
}
}
}
if (trace)
{
for (ControllerState state : stateModel)
{
ControllerState nextState = stateModel.getNextState(state);
Set<ControllerContext> stillUnresolved = getContextsByState(state);
if (stillUnresolved.isEmpty() == false)
{
for (ControllerContext ctx : stillUnresolved)
{
if (advance(ctx))
log.trace("Still unresolved " + nextState.getStateString() + ": " + ctx);
}
}
}
}
// resolve child controllers
for (AbstractController controller : childControllers)
{
controller.lockWrite();
try
{
controller.resolveContexts(trace);
}
finally
{
controller.unlockWrite();
}
}
}
/**
* Resolve contexts<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param fromState the from state
* @param toState the to state
* @param trace whether trace is enabled
* @return true when there were resolutions
*/
protected boolean resolveContexts(ControllerState fromState, ControllerState toState, boolean trace)
{
boolean resolutions = false;
Set<ControllerContext> unresolved = getContextsByState(fromState);
Set<ControllerContext> resolved = resolveContexts(unresolved, fromState, toState, trace);
if (resolved.isEmpty() == false)
{
Set<ControllerContext> toProcess = new HashSet<ControllerContext>();
for (ControllerContext context : resolved)
{
Object name = context.getName();
if (fromState.equals(context.getState()) == false)
{
if (trace)
log.trace("Skipping already installed " + name + " for " + toState.getStateString());
installing.remove(context);
}
else
{
toProcess.add(context);
}
}
try
{
if (toProcess.isEmpty() == false)
{
for (Iterator<ControllerContext> iter = toProcess.iterator(); iter.hasNext(); )
{
ControllerContext context = iter.next();
iter.remove();
Object name = context.getName();
try
{
if (fromState.equals(context.getState()) == false)
{
if (trace)
log.trace("Skipping already installed " + name + " for " + toState.getStateString());
}
else
{
if (trace)
log.trace("Dependencies resolved " + name + " for " + toState.getStateString());
if (executeOrIncrementStateDirectly(context, trace))
{
resolutions = true;
if (trace)
log.trace(name + " " + toState.getStateString());
}
}
}
finally
{
installing.remove(context);
}
}
}
}
finally
{
// If we get here something has gone seriously wrong,
// but try to tidyup as much state as possible
if (toProcess.isEmpty() == false)
{
for (ControllerContext context : toProcess)
installing.remove(context);
}
}
}
return resolutions;
}
/**
* Increment state in the current thread, or asynchonously if the context has {@link ControllerMode#ASYNCHRONOUS} and we find an executor<p>
* <p/>
* This method must be invoked with the write lock taken.
*
* @param context the context
* @param trace whether trace is enabled
* @return whether the increment suceeded. If the context could be incremented asynchronously false is returned
*/
private boolean executeOrIncrementStateDirectly(ControllerContext context, boolean trace)
{
boolean asynch = contextsInstalledByExecutor.shouldInstallAsynchronously(context);
if (asynch)
{
if (executor == null)
{
Object ctx = trace ? context : context.getName();
log.warn("No executor in controller " + this + " to use installing asynchronous context " + ctx);
}
Executor foundExecutor = searchForExecutor();
if (foundExecutor != null)
{
InstallControllerContextTask task = new InstallControllerContextTask(context, trace);
contextsInstalledByExecutor.markForTaskExecution(context, task);
if (trace)
log.trace("Recorded for asynchronous installation " + context.getName());
try
{
foundExecutor.execute(task);
return false;
}
catch(RejectedExecutionException e)
{
Object ctx = trace ? context : context.getName();
log.warn("Asynchronous execution rejected by executor for context " + ctx + ":" + e.getMessage());
contextsInstalledByExecutor.disassociateWithTask(context);
}
}
}
return incrementState(context, trace);
}
/**
* Return the executor stored in this controller or in its parent hierarchy. The nearest executor is returned.
* <p/>
* This method must be called with the write or read lock taken
* @return The found executor
*/
protected Executor searchForExecutor()
{
if (executor != null)
{
return executor;
}
AbstractController parent = getParentController();
if (parent != null)
{
try
{
parent.lockRead();
return parent.searchForExecutor();
}
finally
{
parent.unlockRead();
}
}
return null;
}
/**
* Resolve contexts<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param contexts the contexts
* @param fromState the from state
* @param toState the to state
* @param trace whether trace is enabled
* @return the set of resolved contexts
*/
protected Set<ControllerContext> resolveContexts(Set<ControllerContext> contexts, ControllerState fromState, ControllerState toState, boolean trace)
{
HashSet<ControllerContext> result = new HashSet<ControllerContext>();
if (contexts.isEmpty() == false)
{
for (ControllerContext ctx : contexts)
{
Object name = ctx.getName();
if (fromState.equals(ctx.getState()) == false)
{
if (trace)
log.trace("Skipping already installed " + name + " for " + toState.getStateString());
}
else if (contextsInstalledByExecutor.isInstalledByOtherThread(ctx))
{
if (trace)
log.trace("Installed by other thread " + name);
}
else if (installing.add(ctx) == false)
{
if (trace)
log.trace("Already installing " + name + " for " + toState.getStateString());
}
else if (contextsInstalledByExecutor.isBeingInstalled(ctx) == true)
{
if (trace)
log.trace("Already installing " + name + " for " + toState.getStateString());
installing.remove(ctx);
}
else if (advance(ctx))
{
try
{
if (resolveDependencies(ctx, toState))
result.add(ctx);
else
installing.remove(ctx);
}
catch (Throwable error)
{
installing.remove(ctx);
log.error("Error resolving dependencies for " + toState.getStateString() + ": " + ctx.toShortString(), error);
uninstallContext(ctx, stateModel.getInitialState(), trace);
errorContexts.put(ctx.getName(), ctx);
ctx.setError(error);
}
}
else
{
installing.remove(ctx);
}
}
}
return result;
}
/**
* See if the context has its dependencies resolved to move to the given state.
* <p/>
* This method must be invoked with the write lock taken
*
* @param ctx The context
* @param state The state we want to move to
* @return true if the dependencies are resolved
*/
private boolean resolveDependencies(ControllerContext ctx, ControllerState state)
{
DependencyInfo dependencies = ctx.getDependencyInfo();
return dependencies == null || dependencies.resolveDependencies(this, state);
}
/**
* Uninstall a context
* <p/>
* This method must be invoked with the write lock taken
*
* @param context the context to uninstall
* @param toState the target state
* @param trace whether trace is enabled
*/
protected void uninstallContext(ControllerContext context, ControllerState toState, boolean trace)
{
if (!stateModel.isValidState(toState))
log.error("INTERNAL ERROR: unknown state " + toState + " states=" + stateModel, new Exception("STACKTRACE"));
while (true)
{
ControllerState fromState = context.getState();
if (ControllerState.ERROR.equals(fromState))
return;
if (!stateModel.isValidState(fromState))
log.error("INTERNAL ERROR: current state not found: " + context.toShortString(), new Exception("STACKTRACE"));
if (stateModel.isAfterState(toState, fromState))
return;
else
uninstallContext(context, trace);
}
}
/**
* Uninstall a context<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param context the context to uninstall
* @param trace whether trace is enabled
*/
protected void uninstallContext(ControllerContext context, boolean trace)
{
Object name = context.getName();
ControllerState fromState = context.getState();
if (trace)
log.trace("Uninstalling " + name + " from " + fromState.getStateString());
Controller fromController = context.getController();
Set<ControllerContext> fromContexts = fromController.getContextsByState(fromState);
if (fromContexts == null || fromContexts.remove(context) == false)
throw new Error("INTERNAL ERROR: context not found in previous state " + fromState.getStateString() + " context=" + context.toShortString(), new Exception("STACKTRACE"));
DependencyInfo dependencies = context.getDependencyInfo();
if (dependencies != null)
{
try
{
Set<DependencyItem> dependsOnMe = dependencies.getDependsOnMe(null);
if (dependsOnMe.isEmpty() == false)
{
for (DependencyItem item : dependsOnMe)
{
if (item.isResolved())
{
ControllerState dependentState = item.getDependentState();
if (dependentState == null || dependentState.equals(fromState))
{
if (item.unresolved(this))
{
Set<ControllerContext> dependents = CollectionsFactory.createLazySet();
getContexts(item.getName(), dependents);
if (dependents.isEmpty() == false)
{
ControllerState whenRequired = item.getWhenRequired();
if (whenRequired == null)
whenRequired = stateModel.getInitialState();
for (ControllerContext dependent : dependents)
{
boolean selfDependency = (dependent == context);
if (!selfDependency)
{
contextsInstalledByExecutor.interruptTaskAndBlock(dependent, this);
if (stateModel.isBeforeState(dependent.getState(), whenRequired) == false)
{
uninstallContext(dependent, whenRequired, trace);
}
}
}
}
}
}
}
}
}
}
catch (Throwable error)
{
log.error("Error resolving dependencies for " + fromState.getStateString() + ": " + context.toShortString(), error);
// Not much else we can do - no uninstall, since we are at uninstall ;-)
errorContexts.put(context.getName(), context);
context.setError(error);
}
}
// The state could have changed while calling out to dependents
fromState = context.getState();
if (ControllerState.ERROR.equals(fromState))
return;
ControllerState toState = stateModel.getPreviousState(fromState);
if (toState == null)
{
// This is hack, we signal true uninstalled status by putting it in the error state
context.setState(ControllerState.ERROR);
return;
}
unlockWrite();
try
{
resolveCallbacks(context, fromState, false);
handleUninstallLifecycleCallbacks(context, toState);
uninstall(context, fromState, toState);
Controller toController = context.getController();
Set<ControllerContext> toContexts = toController.getContextsByState(toState);
toContexts.add(context);
context.setState(toState);
uninstallUnusedOnDemandContexts(context, trace);
}
catch (Throwable t)
{
log.warn("Error uninstalling from " + fromState.getStateString() + ": " + context.toShortString(), t);
}
finally
{
lockWrite();
}
}
/**
* If the context being uninstalled depends on On_Demand contexts that are not used anymore
* those On_Demand contexts are uninstalled back to the DESCRIBED state.<p>
*
* This method must be called with NO locks taken.
*
* @param context the context which is being uninstalled.
* @param trace do trace log
*/
protected void uninstallUnusedOnDemandContexts(ControllerContext context, boolean trace)
{
lockWrite();
try
{
DependencyInfo dependencies = context.getDependencyInfo();
if (dependencies != null)
{
Set<DependencyItem> iDependOn = dependencies.getIDependOn(null);
if (iDependOn.isEmpty() == false)
{
for (DependencyItem item : iDependOn)
{
if (item.isResolved()) //TODO Is this check necessary
{
Object name = item.getIDependOn();
if (name == null)
continue;
ControllerContext other = getContext(name, null);
if (other == null)
{
log.warn("Could not find dependency while uninstalling on demand contexts for " + item);
continue;
}
if (other.getMode() != ControllerMode.ON_DEMAND)
continue;
DependencyInfo otherDependencies = other.getDependencyInfo();
if (otherDependencies == null)
continue;
Set<DependencyItem> dependsOnOther = otherDependencies.getDependsOnMe(null);
boolean isRequired = false;
for (DependencyItem dependsOnOtherItem : dependsOnOther)
{
ControllerContext dependsContext = getContext(dependsOnOtherItem.getName(), null);
if (dependsContext == null)
{
log.warn("Could not find dependency while uninstalling on demand contexts for " + item);
continue;
}
ControllerState requiredState = item.getWhenRequired();
ControllerState actualState = dependsContext.getState();
if (requiredState.equals(actualState) || stateModel.isBeforeState(requiredState, actualState))
{
isRequired = true;
break;
}
}
if (!isRequired)
{
//For some reason uninstallContext() uninstalls to the state below the passed in one, add one
ControllerState state = stateModel.getNextState(ControllerMode.ON_DEMAND.getRequiredState());
uninstallContext(other, state, trace);
}
}
}
}
}
}
finally
{
unlockWrite();
}
}
/**
* Get all contexts by name,
* check child controllers as well.
*
* @param name the name of the context
* @param contexts found contexts
*/
protected void getContexts(Object name, Set<ControllerContext> contexts)
{
ControllerContext context = getContext(name, null);
if (context != null)
{
Set<Object> aliases = context.getAliases();
// only pick up unique name
// TODO also ignore alises from @Aliases?
if (aliases == null || aliases.contains(name) == false)
contexts.add(context);
}
Set<AbstractController> children = getControllers();
if (children != null && children.isEmpty() == false)
{
for (AbstractController child : children)
{
child.getContexts(name, contexts);
}
}
}
/**
* Add callback item under demand name.
*
* @param <T> the callback item type
* @param name demand name
* @param isInstallPhase install or uninstall phase
* @param callback callback item
*/
protected <T> void addCallback(Object name, boolean isInstallPhase, CallbackItem<T> callback)
{
lockWrite();
try
{
Map<Object, Set<CallbackItem<?>>> map = (isInstallPhase ? installCallbacks : uninstallCallbacks);
Set<CallbackItem<?>> callbacks = map.get(name);
if (callbacks == null)
{
callbacks = new HashSet<CallbackItem<?>>();
map.put(name, callbacks);
}
callbacks.add(callback);
}
finally
{
unlockWrite();
}
}
/**
* Remove callback item under demand name.
*
* @param <T> the callback item type
* @param name demand name
* @param isInstallPhase install or uninstall phase
* @param callback callback item
*/
protected <T> void removeCallback(Object name, boolean isInstallPhase, CallbackItem<T> callback)
{
lockWrite();
try
{
Map<Object, Set<CallbackItem<?>>> map = (isInstallPhase ? installCallbacks : uninstallCallbacks);
Set<CallbackItem<?>> callbacks = map.get(name);
if (callbacks != null)
{
callbacks.remove(callback);
if (callbacks.isEmpty())
map.remove(name);
}
}
finally
{
unlockWrite();
}
}
/**
* Get calbacks from context.
*
* @param context current context
* @param isInstallPhase install or uninstall phase
* @return callback items from dependency info
*/
protected Set<CallbackItem<?>> getDependencyCallbacks(ControllerContext context, boolean isInstallPhase)
{
DependencyInfo di = context.getDependencyInfo();
if (di != null)
{
return isInstallPhase ? di.getInstallItems() : di.getUninstallItems();
}
return null;
}
/**
* Get the matching callbacks.
*
* @param name demand name
* @param isInstallPhase install or uninstall phase
* @return all matching registered callbacks or empty set if no such item
*/
protected Set<CallbackItem<?>> getCallbacks(Object name, boolean isInstallPhase)
{
lockRead();
try
{
Map<Object, Set<CallbackItem<?>>> map = (isInstallPhase ? installCallbacks : uninstallCallbacks);
Set<CallbackItem<?>> callbacks = map.get(name);
return callbacks != null ? callbacks : Collections.<CallbackItem<?>>emptySet();
}
finally
{
unlockRead();
}
}
/**
* Resolve callbacks.
*
* @param callbacks the callbacks
* @param state current context state
* @param execute do execute callback
* @param isInstallPhase install or uninstall phase
* @param type install or uninstall type
*/
protected void resolveCallbacks(Set<CallbackItem<?>> callbacks, ControllerState state, boolean execute, boolean isInstallPhase, boolean type)
{
if (callbacks != null && callbacks.isEmpty() == false)
{
for (CallbackItem<?> callback : callbacks)
{
if (callback.getWhenRequired().equals(state))
{
if (isInstallPhase)
{
addCallback(callback.getIDependOn(), type, callback);
}
else
{
removeCallback(callback.getIDependOn(), type, callback);
}
if (execute)
{
try
{
callback.ownerCallback(this, isInstallPhase);
}
catch (Throwable t)
{
log.warn("Broken callback: " + callback, t);
}
}
}
}
}
}
/**
* Resolve callback items.
*
* @param context current context
* @param state current context state
* @param isInstallPhase install or uninstall phase
*/
protected void resolveCallbacks(ControllerContext context, ControllerState state, boolean isInstallPhase)
{
ClassLoader previous = null;
try
{
previous = SecurityActions.setContextClassLoader(context);
// existing owner callbacks
Set<CallbackItem<?>> installs = getDependencyCallbacks(context, true);
resolveCallbacks(installs, state, isInstallPhase, isInstallPhase, true);
Set<CallbackItem<?>> uninstalls = getDependencyCallbacks(context, false);
resolveCallbacks(uninstalls, state, isInstallPhase == false, isInstallPhase, false);
// change callbacks, applied only if context is autowire candidate
DependencyInfo dependencyInfo = context.getDependencyInfo();
if (dependencyInfo != null && dependencyInfo.isAutowireCandidate())
{
// match callbacks by name
Set<CallbackItem<?>> existingCallbacks = new HashSet<CallbackItem<?>>();
existingCallbacks.addAll(getCallbacks(context.getName(), isInstallPhase));
// match by classes
Collection<Class<?>> classes = getExposedClasses(context);
if (classes != null && classes.isEmpty() == false)
{
for (Class<?> clazz : classes)
{
existingCallbacks.addAll(getCallbacks(clazz, isInstallPhase));
}
}
// Do the installs if we are at the required state
if (existingCallbacks != null && existingCallbacks.isEmpty() == false)
{
for(CallbackItem<?> callback : existingCallbacks)
{
if (state.equals(callback.getDependentState()))
{
try
{
callback.changeCallback(this, context, isInstallPhase);
}
catch (Throwable t)
{
log.warn("Broken callback: " + callback, t);
}
}
}
}
}
}
// let's make sure we suppress any exceptions
catch (Throwable t)
{
log.warn("Cannot resolve callbacks, state= " + state + ", isInstall= " + isInstallPhase + ", context= " + context, t);
}
finally
{
if (previous != null)
SecurityActions.resetContextClassLoader(previous);
}
}
/**
* Handle install lifecycle callbacks.
*
* @param context the context
* @param state the state
* @throws Throwable for any error
*/
protected void handleInstallLifecycleCallbacks(ControllerContext context, ControllerState state) throws Throwable
{
handleLifecycleCallbacks(context, state, true);
}
/**
* Handle uninstall lifecycle callbacks.
*
* @param context the context
* @param state the state
* @throws Throwable for any error
*/
protected void handleUninstallLifecycleCallbacks(ControllerContext context, ControllerState state) throws Throwable
{
ControllerState oldState = stateModel.getNextState(state);
handleLifecycleCallbacks(context, oldState, false);
}
/**
* Handle lifecycle callbacks.
*
* @param context the context
* @param state the state
* @param install is it install or uninstall
* @throws Throwable for any error
*/
protected void handleLifecycleCallbacks(ControllerContext context, ControllerState state, boolean install) throws Throwable
{
DependencyInfo di = context.getDependencyInfo();
if (di != null)
{
List<LifecycleCallbackItem> callbacks = di.getLifecycleCallbacks();
for (LifecycleCallbackItem callback : callbacks)
{
if (callback.getWhenRequired().equals(state))
{
if (install)
callback.install(context);
else
callback.uninstall(context);
}
}
}
}
/**
* Install a context<p>
* <p/>
* This method must be invoked with NO locks taken
*
* @param context the context
* @param fromState the from state
* @param toState the toState
* @throws Throwable for any error
*/
protected void install(ControllerContext context, ControllerState fromState, ControllerState toState) throws Throwable
{
long time = 0;
boolean collectStats = this.collectStats;
if (collectStats)
time = System.currentTimeMillis();
try
{
context.install(fromState, toState);
}
finally
{
if (collectStats)
{
time = System.currentTimeMillis() - time;
if (time > 0)
{
synchronized (this)
{
if (installStats == null)
installStats = new StateStatistics();
String state = toState.getStateString();
String name = context.getName().toString();
installStats.addStatistic(state, name, time);
}
}
}
}
}
/**
* Uninstall a context<p>
* <p/>
* This method must be invoked with NO locks taken
*
* @param context the context
* @param fromState the from state
* @param toState the to state
*/
protected void uninstall(ControllerContext context, ControllerState fromState, ControllerState toState)
{
context.uninstall(fromState, toState);
}
/**
* Whether we should advance the context<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param context the context
* @return true when we should advance the context
*/
protected boolean advance(ControllerContext context)
{
ControllerMode mode = context.getMode();
// Never advance for disabled
if (ControllerMode.DISABLED.equals(mode))
return false;
return stateModel.isBeforeState(context.getState(), context.getRequiredState());
}
/**
* Lock for read
*/
protected void lockRead()
{
lock.readLock().lock();
}
/**
* Unlock for read
*/
protected void unlockRead()
{
lock.readLock().unlock();
}
/**
* Lock for write
*/
protected void lockWrite()
{
lock.writeLock().lock();
}
/**
* Unlock for write
*/
protected void unlockWrite()
{
lock.writeLock().unlock();
}
/**
* Get a registered context<p>
* <p/>
* This method should be invoked with at least the read lock taken
*
* @param name the name with which to register it
* @param mustExist whether to throw an error when the context does not exist
* @return context the registered context
* @throws IllegalArgumentException for null parameters
* @throws IllegalStateException if the context if must exist is true and the context does not exist
*/
protected ControllerContext getRegisteredControllerContext(Object name, boolean mustExist)
{
if (name == null)
throw new IllegalArgumentException("Null name");
ControllerContext result = allContexts.get(name);
if (mustExist && result == null)
throw new IllegalStateException("Context does not exist with name: " + name);
return result;
}
/**
* Register a context and all its aliases<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param context the context to register
* @throws IllegalArgumentException for null parameters
* @throws IllegalStateException if the context is already registered with that name or alias
*/
protected void registerControllerContext(ControllerContext context)
{
if (context == null)
throw new IllegalArgumentException("Null context");
Set<Object> aliases = context.getAliases();
// Register the context
Object name = context.getName();
registerControllerContext(name, context);
// Register the aliases
if (aliases != null && aliases.isEmpty() == false)
{
int ok = 0;
try
{
for (Object alias : aliases)
{
registerControllerContext(alias, context);
++ok;
}
}
finally
{
// It didn't work
if (ok != aliases.size() && ok > 0)
{
// Unregister the aliases we added
for (Object alias : aliases)
{
if (ok-- == 0)
break;
try
{
unregisterControllerContext(alias);
}
catch (Throwable ignored)
{
log.debug("Error unregistering alias: " + alias, ignored);
}
}
// Unregister the context
try
{
unregisterControllerContext(name);
}
catch (Throwable ignored)
{
log.debug("Error unregistering context with name: " + name, ignored);
}
}
}
}
}
/**
* Unregister a context and all its aliases<p>
* <p/>
* This method must be invoked with the write lock taken
*
* @param context the context
* @throws IllegalArgumentException for null parameters
* @throws IllegalStateException if the context is not registered
*/
protected void unregisterControllerContext(ControllerContext context)
{
if (context == null)
throw new IllegalArgumentException("Null context");
Set<Object> aliases = context.getAliases();
// Unregister the context
Object name = context.getName();
unregisterControllerContext(name);
// Unegister the aliases
if (aliases != null && aliases.isEmpty() == false)
{
for (Object alias : aliases)
{
try
{
unregisterControllerContext(alias);
}
catch (Throwable ignored)
{
log.debug("Error unregistering alias: " + alias, ignored);
}
}
}
}
/**
* Register a context<p>
* <p/>
* This method must be invoked with the write lock taken<p>
* <p/>
* NOTE: You probably want to use the {@link #registerControllerContext(ControllerContext)}
*
* @param name the name with which to register it
* @param context the context to register
* @throws IllegalArgumentException for null parameters
* @throws IllegalStateException if the context is already registered with that name
*/
protected void registerControllerContext(Object name, ControllerContext context)
{
if (name == null)
throw new IllegalArgumentException("Null name");
if (context == null)
throw new IllegalArgumentException("Null context");
if (allContexts.containsKey(name) == false)
allContexts.put(name, context);
else
throw new IllegalStateException("Unable to register context " + context.toShortString() + ", name already exists: " + name);
}
/**
* Unregister a context<p>
* <p/>
* This method must be invoked with the write lock taken<p>
* <p/>
* NOTE: You probably want to use the {@link #unregisterControllerContext(ControllerContext)}
*
* @param name the name it was registered with
* @throws IllegalArgumentException for null parameters
*/
protected void unregisterControllerContext(Object name)
{
if (name == null)
throw new IllegalArgumentException("Null name");
allContexts.remove(name);
}
// --- alias dependency
private static class InnerAliasControllerContext extends AbstractAliasControllerContext
{
private InnerAliasControllerContext(Object alias, String id, Object original, ControllerContextActions actions)
{
super(alias, id, original, actions);
}
}
private class AliasControllerContextAction extends SimpleControllerContextAction<AliasControllerContext>
{
protected AliasControllerContext contextCast(ControllerContext context)
{
return AliasControllerContext.class.cast(context);
}
protected boolean validateContext(ControllerContext context)
{
return (context instanceof AliasControllerContext);
}
protected void installAction(AliasControllerContext context) throws Throwable
{
Object alias = context.getAlias();
Object jmxAlias = JMXObjectNameFix.needsAnAlias(alias);
if (jmxAlias != null)
alias = jmxAlias;
Object original = context.getOriginal();
Object jmxOriginal = JMXObjectNameFix.needsAnAlias(original);
if (jmxOriginal != null)
original = jmxOriginal;
// get the context's controller - not the one we registered with
AbstractController controller = (AbstractController)context.getController();
lockWrite();
try
{
ControllerContext lookup = controller.getRegisteredControllerContext(original, true);
controller.registerControllerContext(alias, lookup);
if (log.isTraceEnabled())
log.trace("Added alias " + alias + " for context " + context);
}
finally
{
unlockWrite();
}
}
protected void uninstallAction(AliasControllerContext context)
{
lockWrite();
try
{
Object alias = context.getAlias();
Object jmxAlias = JMXObjectNameFix.needsAnAlias(alias);
if (jmxAlias != null)
alias = jmxAlias;
// get the context's controller - not the one we registered with
AbstractController controller = (AbstractController)context.getController();
controller.unregisterControllerContext(alias);
if (log.isTraceEnabled())
log.trace("Removed alias " + alias);
}
finally
{
unlockWrite();
}
}
}
public boolean isAsynchronousInstallInProgress(ControllerContext context)
{
if (context.getMode() != ControllerMode.ASYNCHRONOUS)
return false;
return contextsInstalledByExecutor.isBeingInstalled(context);
}
/**
* A task being handled asyynchronously by the executor
*
*/
abstract class InterruptibleControllerTask implements Runnable
{
/**
* The thread used to handle the asynchronous task
*/
Thread thread;
/**
* True if the task is interrupted
*/
volatile boolean interrupted;
/**
* Latch counted down by interrupted tasks once they have reached a state where they finish
*/
CountDownLatch interruptedLatch;
/**
* Gets the thread that is currently used to install the asyncronous task
* @return The thread, or null if still queued in the executor
*/
synchronized Thread getThread()
{
return thread;
}
/**
* Interrupt the task, and if associated with a thread
* return a countdown latch that will have a count of 0
* once the job has broken out
* @return the latch or null if not yet interrupted
*/
synchronized CountDownLatch interrupt()
{
interrupted = true;
if (thread != null)
{
interruptedLatch = new CountDownLatch(1);
return interruptedLatch;
}
return null;
}
/**
* Check the interrupted status
* @return true if interrupted
*/
boolean isInterrupted()
{
return interrupted;
}
/**
* Associate the current thread with the task, or clear it in contextsInstalledByExecutor
* if it has been interrupted. This should be the first thing tasks do once they run.
*
* @param context the context
* @return true if interrupted
*/
synchronized boolean associateWithThreadOrDisassociateIfInterrupted(ControllerContext context)
{
if (interrupted)
{
contextsInstalledByExecutor.disassociateWithTask(context);
return true;
}
thread = Thread.currentThread();
return false;
}
/**
* Disassociate the thread with the task, and if interrupted count down the latch so those
* awaiting the latch know the task has finished.
*/
synchronized void disassociateWithThreadAndSignalEnd()
{
thread = null;
if (interruptedLatch != null)
{
interruptedLatch.countDown();
}
}
}
/**
* Interruptible task used to install a controller context asynchronously.
* It will install the context as far as possible towards its required state,
* before resolving the other contexts.
*/
class InstallControllerContextTask extends InterruptibleControllerTask
{
ControllerContext context;
ClassLoader classLoader;
boolean trace;
public InstallControllerContextTask(ControllerContext context, boolean trace)
{
this.context = context;
this.classLoader = SecurityActions.getContextClassLoader();
this.trace = trace;
}
public void run()
{
if (associateWithThreadOrDisassociateIfInterrupted(context))
return;
if (trace)
log.trace(Thread.currentThread().getName() + " starting asyncronous install of " + context.getName());
lockWrite();
ClassLoader tcl = SecurityActions.setContextClassLoader(classLoader);
try
{
//Move the given context as far through the states as possible
boolean stateChanged = false;
try
{
stateChanged = installMyContext();
}
finally
{
contextsInstalledByExecutor.disassociateWithTask(context);
}
//The given context had its state changed, now see if anybody was dependent on it
if (stateChanged && !interrupted)
{
resolveContexts(trace);
}
}
catch (Throwable t)
{
Object ctx = trace ? context : context.getName();
log.error("Problem installing context asynchronously " + ctx, t);
}
finally
{
if (trace)
log.trace(Thread.currentThread().getName() + " asynchronous install done for " + context.getName());
SecurityActions.resetContextClassLoader(tcl);
unlockWrite();
disassociateWithThreadAndSignalEnd();
}
}
boolean installMyContext()
{
boolean stateChanged = false;
if (!stateModel.isValidState(context.getRequiredState()))
throw new IllegalArgumentException("Unknown state: " + context.getRequiredState());
boolean resolved = true;
while(resolved && stateModel.isBeforeState(context.getState(), context.getRequiredState()) && !interrupted)
{
resolved = false;
ControllerState toState = stateModel.getNextState(context.getState());
if (advance(context))
{
try
{
if (resolveDependencies(context, toState))
{
resolved = true;
}
}
catch (Throwable error)
{
Object ctx = trace ? context : context.getName();
log.error("Error resolving dependencies for " + toState.getStateString() + ": " + ctx, error);
uninstallContext(context, stateModel.getInitialState(), trace);
errorContexts.put(context.getName(), context);
context.setError(error);
}
if (resolved)
{
resolved = incrementState(context, trace);
if (resolved)
{
stateChanged = true;
}
}
}
}
return stateChanged;
}
}
/**
* Class used to keep track of contexts that are scheduled for asynchronous install,
* or are in the process of being installed asynchronously.
*
*/
private static class ContextsInstalledByExecutor
{
/** The contexts that are currently being installed by the executor */
Map<ControllerContext, InterruptibleControllerTask> executorTasksByContext = new ConcurrentHashMap<ControllerContext, InterruptibleControllerTask>();
/**
* Checks whether context should be installed asynchronously, by checking the controller mode and the current thread
* @param context The context
* @return Whether the context should be installed in the executor
*/
boolean shouldInstallAsynchronously(ControllerContext context)
{
if (context.getMode() == ControllerMode.ASYNCHRONOUS)
{
final InterruptibleControllerTask task = executorTasksByContext.get(context);
return task == null || task.getThread() != Thread.currentThread();
}
return false;
}
/**
* Records the context and the asynchronous task to install it
* @param context The context to be installed asynchronously
* @param task The task that will be used to install the context
*/
void markForTaskExecution(ControllerContext context, InterruptibleControllerTask task)
{
executorTasksByContext.put(context, task);
}
/**
* Removes the task that was used to install the context
* @param context The context whose task we want to remove
*/
void disassociateWithTask(ControllerContext context)
{
executorTasksByContext.remove(context);
}
/**
* Checks if the context is recorded for asynchronous install, and if the thread used is different
* from the current thread.
* @param context The context to check
* @return true if installed by another thread
*/
boolean isInstalledByOtherThread(ControllerContext context)
{
final InterruptibleControllerTask task = executorTasksByContext.get(context);
return task != null && Thread.currentThread() != task.getThread();
}
/**
* Checks in the context is recorded for asynchronous install
* @param context The context to check
* @return true if recorded for asynchronous install
*/
boolean isBeingInstalled(ControllerContext context)
{
return executorTasksByContext.get(context) != null;
}
/**
* Checks if the passed in context is recorded for asynchronous install,
* and if it is interrupts the asynchronous task.<p>
*
* This method must be called with the write lock taken
*
* @param context The context to interrupt
* @param controller the controller
*/
void interruptTaskAndBlock(ControllerContext context, AbstractController controller)
{
InterruptibleControllerTask task = executorTasksByContext.get(context);
if (task != null)
{
controller.unlockWrite();
try
{
CountDownLatch latch = task.interrupt();
if (latch != null)
{
try
{
latch.await();
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
}
finally
{
controller.lockWrite();
}
}
}
}
// Context queries & registry
public Set<ControllerContext> filter(Iterable<ControllerContext> contexts, ContextFilter filter)
{
return registry.filter(contexts, filter);
}
public Set<ControllerContext> getInstantiatedContexts(Class<?> clazz)
{
return registry.getInstantiatedContexts(clazz);
}
public Set<ControllerContext> getContexts(Class<?> clazz, ControllerState state)
{
return registry.getContexts(clazz, state);
}
public ControllerContext getContextByClass(Class<?> clazz)
{
return registry.getContextByClass(clazz);
}
public void addInstantiatedContext(ControllerContext context)
{
registry.addInstantiatedContext(context);
}
public void registerInstantiatedContext(ControllerContext context, Class<?>... classes)
{
registry.registerInstantiatedContext(context, classes);
}
public void unregisterInstantiatedContext(ControllerContext context, Class<?>... classes)
{
registry.unregisterInstantiatedContext(context, classes);
}
public void removeInstantiatedContext(ControllerContext context)
{
registry.removeInstantiatedContext(context);
}
public Set<Class<?>> getExposedClasses(ControllerContext context)
{
return registry.getExposedClasses(context);
}
}