Package org.jboss.dependency.plugins

Source Code of org.jboss.dependency.plugins.AbstractController

/*
* 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);
   }
}
TOP

Related Classes of org.jboss.dependency.plugins.AbstractController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.