public interface AddonDeploymentArchive extends Archive<AddonDeploymentArchive>,
         RepositoryLocationAware<AddonDeploymentArchive>, DeploymentTimeoutAware<AddonDeploymentArchive>
{
   /**
    * Get the {@link AddonId} of this {@link AddonDeploymentArchive}.
    */
   AddonId getAddonId();

   /**
    * Set the {@link AddonId} of this {@link AddonDeploymentArchive}.
    */
   AddonDeploymentArchive setAddonId(AddonId id);

   /**
    * Get the {@link DeploymentListener} instances for this {@link AddonDeploymentArchive}
    */
   List<DeploymentListener> getDeploymentListeners();

   /**
    * Set the {@link DeploymentListener} for this {@link AddonDeploymentArchive}
    */
   void addDeploymentListener(DeploymentListener listener);
} public interface Furnace extends AutoCloseable
{
   /**
    * Start this {@link Furnace} instance in a new background {@link Thread}. Return a {@link Future} that can be used
    * to block until the container has started.
    */
   public Future<Furnace> startAsync();

   /**
    * Start this {@link Furnace} instance in a new background {@link Thread}, using the given {@link ClassLoader} to
    * load core implementation resources.Return a {@link Future} that can be used to block until the container has
    * started.
    */
   public Future<Furnace> startAsync(ClassLoader loader);

   /**
    * Start this {@link Furnace} instance and wait for completion.
    */
   public void start();

   /**
    * Start this {@link Furnace} instance and wait for completion, using the given {@link ClassLoader} to load core
    * implementation resources.
    */
   public void start(ClassLoader loader);

   /**
    * Stop this {@link Furnace} instance.
    */
   public Furnace stop();

   /**
    * Returns <code>true</code> or <code>false</code> if {@link Furnace#setServerMode(boolean)} has been set.
    */
   public boolean isServerMode();

   /**
    * When server mode is set to <code>true</code>, {@link Furnace} will run as a server process until explicitly
    * stopped. When server mode is false, {@link Furnace} will process all inputs and exit once addons have completed
    * any requested operations.
    */
   public Furnace setServerMode(boolean server);

   /**
    * Get the central {@link AddonRegistry} for this {@link Furnace} instance. Contains {@link Addon} registration and
    * service information. If specific {@link AddonRepository} instances are supplied, return an registry that functions
    * with the given repositories only.
    */
   public AddonRegistry getAddonRegistry(AddonRepository... repositories);

   /**
    * Get an immutable {@link List} of configured {@link AddonRepository} instances.
    */
   public List<AddonRepository> getRepositories();

   /**
    * Add an {@link AddonRepository} to be scanned for deployed and enabled {@link Addon} instances. This method must
    * not be called once {@link Furnace} is started.
    */
   public AddonRepository addRepository(AddonRepositoryMode mode, File repository);

   /**
    * Add an {@link AddonRepository} to be scanned for deployed and enabled {@link Addon} instances. This method must
    * not be called once {@link Furnace} is started.
    */
   public AddonRepository addRepository(AddonRepository repository);

   /**
    * Get the current runtime API version of {@link Furnace}.
    */
   public Version getVersion();

   /**
    * Register a {@link ContainerLifecycleListener} instance. Returns a {@link ListenerRegistration} that can be used to
    * un-register the listener.
    */
   public ListenerRegistration<ContainerLifecycleListener> addContainerLifecycleListener(
            ContainerLifecycleListener listener);

   /**
    * Get the {@link ClassLoader} from which {@link Furnace} loaded its internal classes.
    */
   public ClassLoader getRuntimeClassLoader();

   /**
    * Get the {@link LockManager} associated with this {@link Furnace} instance
    */
   public LockManager getLockManager();

   /**
    * Get the current status of this {@link Furnace} container
    */
   public ContainerStatus getStatus();

   /**
    * Set the arguments with which {@link Furnace} should start. Typically this will simply be passed through from
    * <code>public static void main(String[] args)</code>.
    */
   public void setArgs(String[] args);

   /**
    * Get the arguments with which {@link Furnace} should start. Typically this will simply be passed through from
    * <code>public static void main(String[] args)</code>.
    *
    * @return the arguments, or <code>null</code> if no arguments were set.
    */
   public String[] getArgs();

   /**
    * Returns <code>true</code> or <code>false</code> if this {@link Furnace} instance is running in a test environment.
    */
   public boolean isTestMode();

   /**
    * Sets the {@link AddonCompatibilityStrategy} for this {@link Furnace} instance. This policy is used to determine
    * version compatibility when loading addons.
    * 
    * Default is {@link AddonCompatibilityStrategies#STRICT}
    */
   public void setAddonCompatibilityStrategy(AddonCompatibilityStrategy policy);

   /**
    * Returns the current {@link AddonCompatibilityStrategy} for this {@link Furnace} instance.
    */
   public AddonCompatibilityStrategy getAddonCompatibilityStrategy();

   /**
    * Stops the server when closed
    */
   @Override
   default void close()
   {
      stop();
   }

   /**
    * Returns the {@link Furnace} instance
    * 
    * @param loader
    * @return
    */
   static Furnace instance(ClassLoader loader)
   {
      for (FurnaceProvider provider : ServiceLoader.load(FurnaceProvider.class, loader))
      {
         return provider.getFurnace(loader);
      }

      throw new IllegalArgumentException("No Furnace container found in the  " + loader
               + ". This method must be called within a running Furnace Addon that references a valid Furnace Container.");
   }
} public class AddonId implements Comparable<AddonId>
{
   private String name;
   private Version apiVersion;
   private Version version;

   /**
    * Get the name of this {@link AddonId}.
    */
   public String getName()
   {
      return name;
   }

   /**
    * Get the API {@link Version} of this {@link AddonId}.
    */
   public Version getApiVersion()
   {
      return apiVersion;
   }

   /**
    * Get the {@link Version} of this {@link AddonId}.
    */
   public Version getVersion()
   {
      return version;
   }

   @Override
   public String toString()
   {
      return toCoordinates();
   }

   /**
    * Used by the converter addon
    */
   public static AddonId valueOf(String coordinates)
   {
      return fromCoordinates(coordinates);
   }

   /**
    * Attempt to parse the given string as {@link Addon} coordinates in the form: "group:name,version"
    *
    * @throws IllegalArgumentException when coordinates are malformed.
    */
   public static AddonId fromCoordinates(final String coordinates) throws IllegalArgumentException
   {
      String[] split = coordinates.split(",");
      List<String> tokens = Arrays.asList(split);

      if (tokens.size() < 2 || tokens.size() > 4)
      {
         throw new IllegalArgumentException(
                  "Coordinates must be of the form 'name,version' or 'name,version,api-version");
      }

      if (tokens.size() >= 3)
      {
         if (tokens.get(2) == null || tokens.get(2).isEmpty())
            throw new IllegalArgumentException("API version was empty [" + coordinates + "]");

         if (tokens.get(1).startsWith("[") | tokens.get(1).startsWith("(")
                  && tokens.get(2).endsWith("]") | tokens.get(2).endsWith(")"))
         {
            if (tokens.size() == 3)
               return from(tokens.get(0), tokens.get(1) + "," + tokens.get(2));
            else if (tokens.size() == 4)
               return from(tokens.get(0), tokens.get(1) + "," + tokens.get(2), tokens.get(3));
         }

         if (tokens.size() > 3)
            throw new IllegalArgumentException(
                     "Coordinates must be of the form 'name,version' or 'name,version,api-version");

         return from(tokens.get(0), tokens.get(1), tokens.get(2));
      }
      return from(tokens.get(0), tokens.get(1));
   }

   /**
    * Create an {@link AddonId} from the given name and version.
    */
   public static AddonId from(String name, String version)
   {
      return from(name, version, null);
   }

   /**
    * Create an {@link AddonId} from the given name and {@link Version}.
    */
   public static AddonId from(String name, Version version)
   {
      return from(name, version, null);
   }

   /**
    * Create an {@link AddonId} from the given name, {@link Version}, and API {@link Version}.
    */
   public static AddonId from(String name, Version version, Version apiVersion)
   {
      Assert.notNull(name, "Name cannot be null.");
      if (name.trim().isEmpty())
         throw new IllegalArgumentException("Name cannot be empty.");
      Assert.notNull(version, "Version cannot be null.");
      if (version.toString().trim().isEmpty())
         throw new IllegalArgumentException("Version cannot be empty.");

      AddonId id = new AddonId();
      id.name = name;
      id.version = version;
      if (apiVersion == null || apiVersion.toString().trim().isEmpty())
         id.apiVersion = EmptyVersion.getInstance();
      else
         id.apiVersion = apiVersion;
      return id;
   }

   /**
    * Create an {@link AddonId} from the given name, version, and API version.
    */
   public static AddonId from(String name, String version, String apiVersion)
   {
      return AddonId.from(name, SingleVersion.valueOf(version),
                          apiVersion == null || apiVersion.trim().isEmpty() ?
                                  EmptyVersion.getInstance() :
                                  SingleVersion.valueOf(apiVersion));
   }

   /**
    * The name and version, comma separated.
    */
   public String toCoordinates()
   {
      return getName() + "," + getVersion();
   }

   @Override
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result + ((version == null) ? 0 : version.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj)
   {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (!(obj instanceof AddonId))
         return false;
      AddonId other = (AddonId) obj;
      if (name == null)
      {
         if (other.getName() != null)
            return false;
      }
      else if (!name.equals(other.getName()))
         return false;
      if (version == null)
      {
         if (other.getVersion() != null)
            return false;
      }
      else if (!version.equals(other.getVersion()))
         return false;
      return true;
   }

   @Override
   public int compareTo(AddonId other)
   {
      if (other == null)
         throw new IllegalArgumentException("Cannot compare against null.");

      int result = getName().compareTo(other.getName());

      if (result == 0)
         result = getVersion().compareTo(other.getVersion());

      if (result == 0)
         result = getApiVersion().compareTo(other.getApiVersion());

      return result;
   }

} public interface AddonRegistry extends AddonView
{
   /**
    * Return an {@link Imported} instance that can be used to obtain all currently available services of the given
    * {@link Class} type.
    * 
    * @return the {@link Imported} (Never null.)
    */
   <T> Imported<T> getServices(Class<T> clazz);

   /**
    * Return an {@link Imported} instance that can be used to obtain all currently available services with
    * {@link Class#getName()} matching the given name.
    * 
    * @return the {@link Imported} (Never null.)
    */
   <T> Imported<T> getServices(String clazz);

   /**
    * Get a {@link Set} of all currently available service types.
    * 
    * @return the {@link Set} of {@link Class} types (Never null.)
    */
   Set<Class<?>> getExportedTypes();

   /**
    * Get a {@link Set} of currently available service types for which {@link Class#isAssignableFrom(Class)} returns
    * <code>true</code>.
    * 
    * @return the {@link Set} of {@link Class} types (Never null.)
    */
   <T> Set<Class<T>> getExportedTypes(Class<T> type);

   /**
    * Return the current version of this {@link AddonRegistry}, which is incremented on every addon {@link PostStartup}
    * and {@link PreShutdown} event for registered {@link Addon} instances. (Starts at 0 and counts up.)
    */
   long getVersion();

   /**
    * The {@link EventManager} bound to this {@link AddonRegistry}
    * 
    * @return a composite {@link EventManager} that delegates calls to {@link Addon#getEventManager()} in
    *         {@link AddonStatus#STARTED} state
    */
   EventManager getEventManager();
} public class FurnaceImpl implements Furnace
{
   public static final String FURNACE_ADDON_COMPATIBILITY_PROPERTY = "furnace.addons.compatibility";
   public static final String FURNACE_LOGGING_LEAK_CLASSLOADERS_PROPERTY = "furnace.logging.leak";
   public static final String FURNACE_DEBUG_PROPERTY = "furnace.debug";
   public static final String TEST_MODE_PROPERTY = "furnace.test.mode";

   private static Logger logger = Logger.getLogger(FurnaceImpl.class.getName());

   private volatile boolean alive = false;
   private volatile ContainerStatus status = ContainerStatus.STOPPED;

   private final ExecutorService executor = Executors.newSingleThreadExecutor();

   private boolean serverMode = true;
   private AddonLifecycleManager manager;
   private final List<ContainerLifecycleListener> registeredListeners = new ArrayList<>();
   private final List<ListenerRegistration<ContainerLifecycleListener>> loadedListenerRegistrations = new ArrayList<>();

   private ClassLoader loader;

   private final Set<RepositoryEntry> repositories = new HashSet<>();

   private final LockManager lock = new LockManagerImpl();

   private String[] args;

   private int registryCount = 0;
   boolean firedAfterStart = false;

   private AddonCompatibilityStrategy addonCompatibilityStrategy = AddonCompatibilityStrategies.STRICT;

   public FurnaceImpl()
   {
      if (!AddonRepositoryImpl.hasRuntimeAPIVersion())
      {
         logger.warning("Could not detect Furnace runtime version - " +
                  "loading all addons, but failures may occur if versions are not compatible.");
      }

      String addonCompatibilityValue = System.getProperty(FURNACE_ADDON_COMPATIBILITY_PROPERTY);
      if (!Strings.isNullOrEmpty(addonCompatibilityValue))
      {
         AddonCompatibilityStrategy strategy = null;
         try
         {
            strategy = AddonCompatibilityStrategies.valueOf(addonCompatibilityValue);
         }
         catch (IllegalArgumentException iae)
         {
            try
            {
               strategy = (AddonCompatibilityStrategy) Class.forName(addonCompatibilityValue).newInstance();
            }
            catch (Exception e)
            {
               logger.log(Level.SEVERE, "Error loading [" + addonCompatibilityValue
                        + "] defined by system property `" + FURNACE_ADDON_COMPATIBILITY_PROPERTY + "` ", e);
            }
         }
         if (strategy == null)
         {
            logger.warning("'" + addonCompatibilityValue + "' is not a valid value for the '"
                     + FURNACE_ADDON_COMPATIBILITY_PROPERTY + "' property. Possible values are: "
                     + Arrays.toString(AddonCompatibilityStrategies.values())
                     + " or a fully qualified class name. Assuming default value.");
         }
         else
         {
            setAddonCompatibilityStrategy(strategy);
         }
      }

      if (!Boolean.getBoolean(FURNACE_LOGGING_LEAK_CLASSLOADERS_PROPERTY))
      {
         /*
          * If enabled, allows the JDK java.util.logging.Level to leak ClassLoaders (memory leak).
          */
         LoggingRepair.init();
      }

      if (Boolean.getBoolean(FURNACE_DEBUG_PROPERTY))
      {
         /*
          * If enabled, prints a LOT of debug logging from JBoss Modules.
          */
         enableLogging();
      }
   }

   @Override
   public LockManager getLockManager()
   {
      return lock;
   }

   @Override
   public ClassLoader getRuntimeClassLoader()
   {
      return loader;
   }

   public Furnace enableLogging()
   {
      assertNotAlive();
      Module.setModuleLogger(new StreamModuleLogger(System.err));
      return this;
   }

   @Override
   public Future<Furnace> startAsync()
   {
      return startAsync(FurnaceImpl.class.getClassLoader());
   }

   @Override
   public Future<Furnace> startAsync(final ClassLoader loader)
   {
      return executor.submit(new Callable<Furnace>()
      {
         @Override
         public Furnace call() throws Exception
         {
            Thread thread = new Thread()
            {
               @Override
               public void run()
               {
                  Thread.currentThread().setName("Furnace Container " + FurnaceImpl.this);
                  FurnaceImpl.this.start(loader);
               }
            };
            thread.start();

            while (!ContainerStatus.STARTED.equals(getStatus()))
               Thread.sleep(25);

            return FurnaceImpl.this;
         }
      });
   }

   @Override
   public void start()
   {
      start(FurnaceImpl.class.getClassLoader());
   }

   @Override
   public void start(ClassLoader loader)
   {
      logger.log(Level.INFO, "Furnace [" + getVersion() + "] starting.");
      assertNotAlive();
      alive = true;

      this.loader = loader;

      for (ContainerLifecycleListener listener : ServiceLoader.load(ContainerLifecycleListener.class, loader))
      {
         ListenerRegistration<ContainerLifecycleListener> registration = addContainerLifecycleListener(listener);
         loadedListenerRegistrations.add(registration);
      }

      fireBeforeContainerStartedEvent();

      try
      {
         getAddonRegistry();
         do
         {
            lock.performLocked(LockMode.WRITE, new Callable<Void>()
            {
               @Override
               public Void call() throws Exception
               {
                  boolean dirty = false;
                  if (!getLifecycleManager().isStartingAddons())
                  {
                     for (RepositoryEntry entry : repositories)
                     {
                        DirtyChecker dirtyChecker = entry.getDirtyChecker();
                        if (dirtyChecker.isDirty())
                        {
                           logger.log(Level.FINE, "Detected changes in repository [" + entry.getRepository() + "].");
                           dirty = true;
                        }
                        dirtyChecker.resetDirtyStatus();
                     }

                     if (dirty)
                     {
                        reloadConfiguration();
                     }
                  }

                  status = ContainerStatus.STARTED;
                  if (!firedAfterStart)
                  {
                     fireAfterContainerStartedEvent();
                     firedAfterStart = true;
                  }
                  return null;
               }
            });
            Thread.sleep(100);
         }
         while (isAlive() && serverMode);

         while (isAlive() && getLifecycleManager().isStartingAddons())
         {
            Thread.sleep(100);
         }
      }
      catch (Exception e)
      {
         logger.log(Level.SEVERE, "Error occurred.", e);
      }
      finally
      {
         fireBeforeContainerStoppedEvent();
         status = ContainerStatus.STOPPED;
         getLifecycleManager().stopAll();
      }

      fireAfterContainerStoppedEvent();
      cleanup();
   }

   @Override
   public Furnace stop()
   {
      alive = false;
      return this;
   }

   @Override
   public void setArgs(String[] args)
   {
      assertNotAlive();
      this.args = args;
   }

   @Override
   public String[] getArgs()
   {
      return args;
   }

   @Override
   public boolean isServerMode()
   {
      return serverMode;
   }

   @Override
   public Furnace setServerMode(boolean server)
   {
      assertNotAlive();
      this.serverMode = server;
      return this;
   }

   @Override
   public AddonRegistry getAddonRegistry(final AddonRepository... repositories)
   {
      assertIsAlive();

      AddonRegistry result = getLifecycleManager().findView(repositories);

      if (result == null)
      {
         result = lock.performLocked(LockMode.WRITE, new Callable<AddonRegistry>()
         {
            @Override
            public AddonRegistry call() throws Exception
            {
               AddonRegistry registry = getLifecycleManager().findView(repositories);
               if (registry == null)
               {
                  if (repositories == null || repositories.length == 0)
                  {
                     String name = "ROOT" + "_" + UUID.randomUUID().toString();
                     registry = new AddonRegistryImpl(lock, getLifecycleManager(), getRepositories(), name);
                  }
                  else
                  {
                     String name = String.valueOf(registryCount++ + "_" + UUID.randomUUID().toString());
                     registry = new AddonRegistryImpl(lock, getLifecycleManager(), Arrays.asList(repositories), name);
                  }
                  getLifecycleManager().addView(registry);
                  getLifecycleManager().forceUpdate();
               }
               return registry;
            }
         });
      }

      return result;
   }

   public void disposeAddonView(AddonView view)
   {
      assertIsAlive();

      if (getAddonRegistry().equals(view))
         throw new IllegalArgumentException(
                  "Cannot dispose the root AddonRegistry. Call .stop() instead.");

      getLifecycleManager().removeView(view);
      getLifecycleManager().forceUpdate();
   }

   @Override
   public Version getVersion()
   {
      return AddonRepositoryImpl.getRuntimeAPIVersion();
   }

   @Override
   public ListenerRegistration<ContainerLifecycleListener> addContainerLifecycleListener(
            final ContainerLifecycleListener listener)
   {
      registeredListeners.add(listener);
      return new ListenerRegistration<ContainerLifecycleListener>()
      {
         @Override
         public ContainerLifecycleListener removeListener()
         {
            registeredListeners.remove(listener);
            return listener;
         }
      };
   }

   @Override
   public List<AddonRepository> getRepositories()
   {
      return repositories.stream().map(RepositoryEntry::getRepository).collect(Collectors.toList());
   }

   @Override
   public AddonRepository addRepository(AddonRepositoryMode mode, File directory)
   {
      Assert.notNull(mode, "Addon repository mode must not be null.");
      Assert.notNull(directory, "Addon repository directory must not be null.");

      AddonRepository repository = AddonRepositoryImpl.forDirectory(this, directory);

      if (mode.isImmutable())
         repository = new ImmutableAddonRepository(repository);
      return addRepository(repository);
   }

   @Override
   public AddonRepository addRepository(final AddonRepository repository)
   {
      Assert.notNull(repository, "Addon repository must not be null.");

      final DirtyChecker dirtyChecker;
      if (repository instanceof DirtyCheckableRepository)
      {
         dirtyChecker = ((DirtyCheckableRepository) repository).createDirtyChecker();
      }
      else
      {
         dirtyChecker = new VersionDirtyChecker(repository::getVersion);
      }

      RepositoryEntry newEntry = new RepositoryEntry(repository, dirtyChecker);

      for (RepositoryEntry entry : repositories)
      {
         if (entry.equals(newEntry))
         {
            return entry.getRepository();
         }
      }

      lock.performLocked(LockMode.WRITE, new Callable<Void>()
      {
         @Override
         public Void call() throws Exception
         {
            /*
             * The existing ROOT view must be updated *before*
             */
            if (isAlive())
            {
               AddonRegistry registry = getAddonRegistry();
               ((AddonRegistryImpl) registry).addRepository(repository);
            }
            repositories.add(newEntry);
            return null;
         }
      });

      return repository;
   }

   public void assertIsAlive()
   {
      if (!isAlive())
         throw new IllegalStateException(
                  "Cannot access this method until Furnace is running. Call .start() or .startAsync() first.");
   }

   public void assertNotAlive()
   {
      if (isAlive())
         throw new IllegalStateException("Cannot modify a running Furnace instance. Call .stop() first.");
   }

   @Override
   public ContainerStatus getStatus()
   {
      return lock.performLocked(LockMode.READ, new Callable<ContainerStatus>()
      {
         @Override
         public ContainerStatus call() throws Exception
         {
            if (!isAlive())
               return ContainerStatus.STOPPED;

            boolean startingAddons = getLifecycleManager().isStartingAddons();
            return startingAddons ? ContainerStatus.STARTING : status;
         }
      });
   }

   public List<ContainerLifecycleListener> getRegisteredListeners()
   {
      return Collections.unmodifiableList(registeredListeners);
   }

   public AddonLifecycleManager getAddonLifecycleManager()
   {
      return getLifecycleManager();
   }

   @Override
   public String toString()
   {
      return getLifecycleManager().toString();
   }

   @Override
   public boolean isTestMode()
   {
      return Boolean.getBoolean(TEST_MODE_PROPERTY);
   }

   @Override
   public void setAddonCompatibilityStrategy(final AddonCompatibilityStrategy strategy)
   {
      Assert.notNull(strategy, "AddonCompatibilityStrategy cannot be null");
      if (isAlive())
      {
         lock.performLocked(LockMode.WRITE, new Callable<Void>()
         {
            @Override
            public Void call() throws Exception
            {
               FurnaceImpl.this.addonCompatibilityStrategy = strategy;
               reloadConfiguration();
               return null;
            }
         });
      }
      else
      {
         FurnaceImpl.this.addonCompatibilityStrategy = strategy;
      }
   }

   @Override
   public AddonCompatibilityStrategy getAddonCompatibilityStrategy()
   {
      return this.addonCompatibilityStrategy;
   }

   /*
    * Internal methods.
    */
   private AddonLifecycleManager getLifecycleManager()
   {
      if (manager == null)
         manager = new AddonLifecycleManager(this);
      return manager;
   }

   private boolean isAlive()
   {
      return alive;
   }

   private void reloadConfiguration()
   {
      if (status.isStarted())
         status = ContainerStatus.RELOADING;

      try
      {
         fireBeforeConfigurationScanEvent();
         getLifecycleManager().forceUpdate();
         fireAfterConfigurationScanEvent();
      }
      catch (Exception e)
      {
         logger.log(Level.SEVERE, "Error occurred.", e);
      }

      if (status.isReloading())
         status = ContainerStatus.STARTED;
   }

   private void cleanup()
   {
      for (ListenerRegistration<ContainerLifecycleListener> registation : loadedListenerRegistrations)
      {
         registation.removeListener();
      }
      registeredListeners.clear();
      loader = null;
      manager.dispose();
      manager = null;
      for (RepositoryEntry entry : repositories)
      {
         try
         {
            entry.getDirtyChecker().close();
         }
         catch (Exception e)
         {
            logger.log(Level.SEVERE, "Error occurred.", e);
         }
      }
      repositories.clear();
      executor.shutdownNow();
      firedAfterStart = false;
   }

   private void fireBeforeConfigurationScanEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.beforeConfigurationScan(this);
      }
   }

   private void fireAfterConfigurationScanEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.afterConfigurationScan(this);
      }
   }

   private void fireBeforeContainerStartedEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.beforeStart(this);
      }
   }

   private void fireBeforeContainerStoppedEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.beforeStop(this);
      }
   }

   private void fireAfterContainerStartedEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.afterStart(this);
      }
   }

   private void fireAfterContainerStoppedEvent()
   {
      for (ContainerLifecycleListener listener : registeredListeners)
      {
         listener.afterStop(this);
      }
   }

   private static class RepositoryEntry
   {
      private final AddonRepository repository;

      private final DirtyChecker dirtyChecker;

      public RepositoryEntry(AddonRepository repository, DirtyChecker dirtyChecker)
      {
         this.repository = repository;
         this.dirtyChecker = dirtyChecker;
      }

      public AddonRepository getRepository()
      {
         return repository;
      }

      public DirtyChecker getDirtyChecker()
      {
         return dirtyChecker;
      }

      @Override
      public boolean equals(Object o)
      {
         if (this == o)
            return true;
         if (o == null || getClass() != o.getClass())
            return false;

         RepositoryEntry that = (RepositoryEntry) o;

         return repository.getRootDirectory().equals(that.repository.getRootDirectory());
      }

      @Override
      public int hashCode()
      {
         return repository.hashCode();
      }
   }
} public class AddonManagerImpl implements AddonManager
{
   private final Furnace furnace;
   private final AddonDependencyResolver resolver;
   private final AddonView addonView;

   public AddonManagerImpl(final Furnace furnace, final AddonDependencyResolver resolver)
   {
      this.furnace = furnace;
      this.resolver = resolver;
      this.addonView = null;
   }

   public AddonManagerImpl(final Furnace forge, final AddonDependencyResolver resolver, final AddonView addonView)
   {
      this.furnace = forge;
      this.resolver = resolver;
      this.addonView = addonView;
   }

   @Override
   public AddonInfo info(final AddonId addonId)
   {
      return resolver.resolveAddonDependencyHierarchy(addonId);
   }

   @Override
   public InstallRequest install(final AddonId addonId)
   {
      return install(addonId, getDefaultRepository());
   }

   @Override
   public InstallRequest install(final AddonId addonId, final AddonRepository repository)
   {
      MutableAddonRepository mutableRepo = assertMutableRepository(repository);
      AddonInfo addonInfo = info(addonId);
      List<AddonInfo> allAddons = collectRequiredAddons(addonInfo);
      Map<AddonId, AddonRepository> installedAddonIds = getInstalledAddons();
      List<AddonActionRequest> actions = new ArrayList<>();
      for (AddonInfo newAddonInfo : allAddons)
      {
         AddonActionRequest request = createRequest(addonInfo, newAddonInfo, mutableRepo, installedAddonIds);
         if (request != null)
         {
            actions.add(request);
         }
      }
      return createInstallRequest(addonInfo, actions);
   }

   @Override
   public DeployRequest deploy(AddonId id)
   {
      return deploy(id, getDefaultRepository());
   }

   @Override
   public DeployRequest deploy(AddonId id, AddonRepository repository)
   {
      MutableAddonRepository mutableRepo = assertMutableRepository(repository);
      return createDeployRequest(info(id), mutableRepo, furnace);
   }

   @Override
   public RemoveRequest remove(final AddonId id)
   {
      return remove(id, getDefaultRepository());
   }

   @Override
   public RemoveRequest remove(final AddonId id, final AddonRepository repository)
   {
      AddonInfo info = new ShallowAddonInfo(id);
      return createRemoveRequest(info, assertMutableRepository(repository), furnace);
   }

   @Override
   public DisableRequest disable(final AddonId id)
   {
      return disable(id, getDefaultRepository());
   }

   @Override
   public DisableRequest disable(final AddonId id, final AddonRepository repository)
   {
      AddonInfo info = new ShallowAddonInfo(id);
      return createDisableRequest(info, assertMutableRepository(repository), furnace);
   }

   @Override
   public EnableRequest enable(final AddonId id)
   {
      return enable(id, getDefaultRepository());
   }

   @Override
   public EnableRequest enable(final AddonId id, final AddonRepository repository)
   {
      AddonInfo info = new ShallowAddonInfo(id);
      return createEnableRequest(info, assertMutableRepository(repository), furnace);
   }

   /**
    * Calculate the necessary request based in the list of installed addons for a given {@link MutableAddonRepository}
    *
    * @param addonInfo
    * @param repository
    * @param installedAddons
    * @return
    */
   private AddonActionRequest createRequest(final AddonInfo requestedAddonInfo, final AddonInfo addonInfo,
            final MutableAddonRepository repository,
            final Map<AddonId, AddonRepository> installedAddons)
   {
      final AddonActionRequest request;
      AddonId addon = addonInfo.getAddon();
      if (installedAddons.containsKey(addon))
      {
         // Already contains the installed addon. Update ONLY if the version is SNAPSHOT and if it is the requested
         // addon
         if (Versions.isSnapshot(addon.getVersion()) && addonInfo.equals(requestedAddonInfo))
         {
            AddonRepository addonRepository = installedAddons.get(addon);
            if (repository.equals(addonRepository))
            {
               request = createUpdateRequest(addonInfo, addonInfo, repository, furnace);
            }
            else
            {
               request = createDeployRequest(addonInfo, repository, furnace);
            }
         }
         else
         {
            request = null;
         }
      }
      else
      {
         // Addon is not installed or has a different version
         Entry<AddonId, AddonRepository> differentVersionEntry = null;
         for (Entry<AddonId, AddonRepository> addonEntry : installedAddons.entrySet())
         {
            AddonId addonId = addonEntry.getKey();
            if (addonId.getName().equals(addon.getName()))
            {
               differentVersionEntry = addonEntry;
               break;
            }
         }
         if (differentVersionEntry != null)
         {
            // Avoiding ClassCastExceptions
            Version differentVersion = SingleVersion.valueOf(differentVersionEntry.getKey().getVersion().toString());
            Version addonVersion = SingleVersion.valueOf(addon.getVersion().toString());
            // TODO: Review condition below
            // Update ONLY if it is the requested addon
            if (differentVersion.compareTo(addonVersion) < 0 && addonInfo.equals(requestedAddonInfo))
            {
               if (repository.equals(differentVersionEntry.getValue()))
               {
                  request = createUpdateRequest(info(differentVersionEntry.getKey()), addonInfo, repository, furnace);
               }
               else
               {
                  request = createDeployRequest(addonInfo, repository, furnace);
               }
            }
            else
            {
               // No update needed. Don't do anything with it
               request = null;
            }
         }
         else
         {
            request = createDeployRequest(addonInfo, repository, furnace);
         }
      }
      return request;
   }

   private List<AddonInfo> collectRequiredAddons(final AddonInfo addonInfo)
   {
      return furnace.getLockManager().performLocked(LockMode.READ, new Callable<List<AddonInfo>>()
      {
         @Override
         public List<AddonInfo> call() throws Exception
         {
            List<AddonInfo> allAddons = new LinkedList<>();
            collectRequiredAddons(addonInfo, allAddons);
            return allAddons;
         }
      });
   }

   /**
    * Collect all required addons for a specific addon.
    *
    * It traverses the whole graph
    *
    * @param addonInfo
    * @param addons
    */
   private void collectRequiredAddons(AddonInfo addonInfo, List<AddonInfo> addons)
   {
      // Move this addon to the top of the list
      addons.remove(addonInfo);
      addons.add(0, addonInfo);
      for (AddonId addonId : addonInfo.getRequiredAddons())
      {
         if (!addons.contains(addonId) && (!isDeployed(addonId) || !isEnabled(addonId)))
         {
            AddonInfo childInfo = info(addonId);
            collectRequiredAddons(childInfo, addons);
         }
      }
   }

   private MutableAddonRepository getDefaultRepository()
   {
      for (AddonRepository repo : getRepositories())
      {
         if (repo instanceof MutableAddonRepository)
            return (MutableAddonRepository) repo;
      }
      throw new IllegalStateException(
               "No default mutable repository found in Furnace instance. Have you added one using furnace.addRepository(AddonRepositoryMode.MUTABLE, repository) ?");
   }

   private MutableAddonRepository assertMutableRepository(AddonRepository repository)
   {
      Assert.isTrue(repository instanceof MutableAddonRepository, "Addon repository ["
               + repository.getRootDirectory().getAbsolutePath()
               + "] is not writable.");
      return (MutableAddonRepository) repository;
   }

   /**
    * Returns a {@link Map} of the installed addons with the a key of {@link AddonId} and the value of
    * {@link AddonRepository} indicating in which repository the addon is installed.
    */
   private Map<AddonId, AddonRepository> getInstalledAddons()
   {
      Map<AddonId, AddonRepository> addons = new HashMap<>();
      for (AddonRepository repository : getRepositories())
      {
         List<AddonId> listEnabled = repository.listAll();
         for (AddonId addonId : listEnabled)
         {
            addons.put(addonId, repository);
         }
      }
      return addons;
   }

   private boolean isDeployed(AddonId addonId)
   {
      for (AddonRepository repository : getRepositories())
      {
         if (repository.isDeployed(addonId))
            return true;
      }
      return false;
   }

   private boolean isEnabled(AddonId addonId)
   {
      for (AddonRepository repository : getRepositories())
      {
         if (repository.isEnabled(addonId))
            return true;
      }
      return false;
   }

   private Collection<AddonRepository> getRepositories()
   {
      return (addonView == null) ? furnace.getRepositories() : addonView.getRepositories();
   }
} public class MavenContainer
{
   private static final String M2_HOME = System.getenv().get("M2_HOME");

   /**
    * Sets an alternate location to Maven user settings.xml configuration
    */
   public static final String ALT_USER_SETTINGS_XML_LOCATION = "org.apache.maven.user-settings";

   /**
    * Sets an alternate location of Maven global settings.xml configuration
    */
   public static final String ALT_GLOBAL_SETTINGS_XML_LOCATION = "org.apache.maven.global-settings";

   /**
    * Sets an alternate location of Maven local repository
    */
   public static final String ALT_LOCAL_REPOSITORY_LOCATION = "maven.repo.local";

   /**
    * Get a list of all {@link RemoteRepository} instances from the Maven settings. The returned list can be used as is
    * in a CollectRequest or a VersionRangeRequest, since all repositories and mirrors have been enriched with the
    * required authentication info.
    * 
    * @param settings The Maven settings instance.
    * @return A list of {@link RemoteRepository} instances
    */
   public List<RemoteRepository> getEnabledRepositoriesFromProfile(Settings settings)
   {
      Set<RemoteRepository> settingsRepos = new HashSet<>();
      List<String> activeProfiles = settings.getActiveProfiles();

      // "Active by default" profiles must be added separately, since they are not recognized as active ones
      for (Profile profile : settings.getProfiles())
      {
         Activation activation = profile.getActivation();
         if (activation != null && activation.isActiveByDefault())
         {
            activeProfiles.add(profile.getId());
         }
      }

      Map<String, Profile> profiles = settings.getProfilesAsMap();

      // Collect all repositories declared in all active profiles
      for (String id : activeProfiles)
      {
         Profile profile = profiles.get(id);
         if (profile != null)
         {
            List<Repository> repositories = profile.getRepositories();
            for (Repository repository : repositories)
            {
               settingsRepos.add(new RemoteRepository.Builder(repository.getId(), repository.getLayout(), repository
                        .getUrl()).build());
            }
         }
      }

      final DefaultMirrorSelector mirrorSelector = createMirrorSelector(settings);

      final List<RemoteRepository> mirrorsForSettingsRepos = new ArrayList<>();
      for (Iterator<RemoteRepository> iter = settingsRepos.iterator(); iter.hasNext();)
      {
         RemoteRepository settingsRepository = iter.next();
         RemoteRepository repoMirror = mirrorSelector.getMirror(settingsRepository);
         // If a mirror is available for a repository, then remove the repo, and use the mirror instead
         if (repoMirror != null)
         {
            iter.remove();
            mirrorsForSettingsRepos.add(repoMirror);
         }
      }
      // We now have a collection of mirrors and unmirrored repositories.
      settingsRepos.addAll(mirrorsForSettingsRepos);

      Set<RemoteRepository> enrichedRepos = new HashSet<>();
      LazyAuthenticationSelector authSelector = createAuthSelector(settings, mirrorSelector);
      for (RemoteRepository settingsRepo : settingsRepos)
      {
         // Obtain the Authentication for the repository or it's mirror
         Authentication auth = authSelector.getAuthentication(settingsRepo);
         // All RemoteRepositories (Mirrors and Repositories) constructed so far lack Authentication info.
         // Use the settings repo as the prototype and create an enriched repo with the Authentication.
         enrichedRepos.add(new RemoteRepository.Builder(settingsRepo).setAuthentication(auth).build());
      }
      return new ArrayList<>(enrichedRepos);
   }

   public Settings getSettings()
   {
      try
      {
         SettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance();
         SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();
         String userSettingsLocation = System.getProperty(ALT_USER_SETTINGS_XML_LOCATION);
         // TeamCity sets the settings.xml in a different system property
         String teamCitySettingsLocation = System.getProperty("teamcity.maven.userSettings.path");
         if (userSettingsLocation != null)
         {
            settingsRequest.setUserSettingsFile(new File(userSettingsLocation));
         }
         else if (teamCitySettingsLocation != null)
         {
            settingsRequest.setUserSettingsFile(new File(teamCitySettingsLocation));
         }
         else
         {
            settingsRequest.setUserSettingsFile(new File(getUserHomeDir(), "/.m2/settings.xml"));
         }
         String globalSettingsLocation = System.getProperty(ALT_GLOBAL_SETTINGS_XML_LOCATION);
         if (globalSettingsLocation != null)
         {
            settingsRequest.setGlobalSettingsFile(new File(globalSettingsLocation));
         }
         else
         {
            if (M2_HOME != null)
            {
               settingsRequest.setGlobalSettingsFile(new File(M2_HOME, "/conf/settings.xml"));
            }
         }
         SettingsBuildingResult settingsBuildingResult = settingsBuilder.build(settingsRequest);
         Settings effectiveSettings = settingsBuildingResult.getEffectiveSettings();

         if (effectiveSettings.getLocalRepository() == null)
         {
            String userRepositoryLocation = System.getProperty(ALT_LOCAL_REPOSITORY_LOCATION);
            if (userRepositoryLocation != null)
            {
               effectiveSettings.setLocalRepository(userRepositoryLocation);
            }
            else
            {
               effectiveSettings.setLocalRepository(getUserHomePath() + "/.m2/repository");
            }
         }

         return effectiveSettings;
      }
      catch (SettingsBuildingException e)
      {
         throw new RuntimeException(e);
      }
   }

   public RepositorySystem getRepositorySystem()
   {

      final DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
      locator.setServices(ModelBuilder.class, new DefaultModelBuilderFactory().newInstance());
      // Installing Wagon to fetch from HTTP repositories
      locator.setServices(WagonProvider.class, new ManualWagonProvider());
      locator.addService(TransporterFactory.class, WagonTransporterFactory.class);
      locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
      final RepositorySystem repositorySystem = locator.getService(RepositorySystem.class);
      return repositorySystem;
   }

   public static org.eclipse.aether.repository.Proxy convertFromMavenProxy(org.apache.maven.settings.Proxy proxy)
   {
      org.eclipse.aether.repository.Proxy result = null;
      if (proxy != null)
      {
         Authentication auth = new AuthenticationBuilder().addUsername(proxy.getUsername())
                  .addPassword(proxy.getPassword()).build();
         result = new org.eclipse.aether.repository.Proxy(proxy.getProtocol(), proxy.getHost(), proxy.getPort(), auth);
      }
      return result;
   }

   private File getUserHomeDir()
   {
      return new File(System.getProperty("user.home")).getAbsoluteFile();
   }

   private String getUserHomePath()
   {
      return getUserHomeDir().getAbsolutePath();
   }

   public DefaultRepositorySystemSession setupRepoSession(final RepositorySystem repoSystem, final Settings settings)
   {
      DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
      session.setOffline(false);

      Proxy activeProxy = settings.getActiveProxy();
      if (activeProxy != null)
      {
         DefaultProxySelector dps = new DefaultProxySelector();
         dps.add(convertFromMavenProxy(activeProxy), activeProxy.getNonProxyHosts());
         session.setProxySelector(dps);
      }

      final DefaultMirrorSelector mirrorSelector = createMirrorSelector(settings);
      final LazyAuthenticationSelector authSelector = createAuthSelector(settings, mirrorSelector);

      session.setMirrorSelector(mirrorSelector);
      session.setAuthenticationSelector(authSelector);

      LocalRepository localRepo = new LocalRepository(new File(settings.getLocalRepository()));
      session.setLocalRepositoryManager(repoSystem.newLocalRepositoryManager(session, localRepo));
      session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_IGNORE);
      session.setCache(new DefaultRepositoryCache());
      boolean cacheNotFoundArtifacts = true;
      boolean cacheTransferErrors = true;
      session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(cacheNotFoundArtifacts, cacheTransferErrors));
      session.setWorkspaceReader(new ClasspathWorkspaceReader());
      if (Boolean.getBoolean("org.apache.maven.log_transfer"))
      {
         session.setTransferListener(new JULMavenTransferListener());
      }
      return session;
   }

   private DefaultMirrorSelector createMirrorSelector(Settings settings)
   {
      final DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
      final List<Mirror> mirrors = settings.getMirrors();
      if (mirrors != null)
      {
         for (Mirror mirror : mirrors)
         {
            mirrorSelector.add(mirror.getId(), mirror.getUrl(), mirror.getLayout(), false, mirror.getMirrorOf(),
                     mirror.getMirrorOfLayouts());
         }
      }
      return mirrorSelector;
   }

   private LazyAuthenticationSelector createAuthSelector(final Settings settings,
            final DefaultMirrorSelector mirrorSelector)
   {
      LazyAuthenticationSelector authSelector = new LazyAuthenticationSelector(mirrorSelector);
      for (Server server : settings.getServers())
      {
         authSelector.add(
                  server.getId(),
                  new AuthenticationBuilder().addUsername(server.getUsername()).addPassword(server.getPassword())
                           .addPrivateKey(server.getPrivateKey(), server.getPassphrase()).build());
      }
      return authSelector;
   }

} public class MavenAddonDependencyResolver implements AddonDependencyResolver
{
   private static final String FURNACE_API_GROUP_ID = "org.jboss.forge.furnace";
   private static final String FURNACE_CONTAINER_GROUP_ID = "org.jboss.forge.furnace.container";
   private static final String FURNACE_API_ARTIFACT_ID = "furnace-api";

   public static final String FORGE_ADDON_CLASSIFIER = "forge-addon";
   private final String classifier;
   private Settings settings;
   private boolean resolveAddonAPIVersions = true;
   private final MavenContainer container = new MavenContainer();

   public MavenAddonDependencyResolver()
   {
      classifier = FORGE_ADDON_CLASSIFIER;
   }

   public MavenAddonDependencyResolver(String classifier)
   {
      Assert.notNull(classifier, "Classifier should not be null");
      this.classifier = classifier;
   }

   @Override
   public AddonInfo resolveAddonDependencyHierarchy(AddonId addonId)
   {
      AddonInfoBuilder builder = AddonInfoBuilder.from(addonId);
      try
      {
         ArtifactDescriptorResult result = readArtifactDescriptor(addonId);
         AddonId furnaceContainerId = null;
         for (Dependency dependency : result.getDependencies())
         {
            String scope = dependency.getScope();
            // Skip test scoped dependencies
            if (JavaScopes.TEST.equals(scope))
               continue;
            Artifact artifact = dependency.getArtifact();
            // Searching for the API version
            if (isFurnaceAPI(artifact))
            {
               SingleVersion apiVersion = SingleVersion.valueOf(artifact.getBaseVersion());
               builder.setAPIVersion(apiVersion);
            }
            else if (isAddon(artifact))
            {
               AddonId childId = toAddonId(artifact);
               if (isFurnaceContainer(artifact))
               {
                  furnaceContainerId = childId;
               }
               boolean exported = isExported(scope);
               boolean optional = dependency.isOptional();
               if (optional)
               {
                  builder.addOptionalDependency(childId, exported);
               }
               else
               {
                  builder.addRequiredDependency(childId, exported);
               }
            }
         }
         org.jboss.forge.furnace.versions.Version addonAPIVersion = builder.getAddon().getApiVersion();
         if (resolveAddonAPIVersions && (addonAPIVersion == null || EmptyVersion.getInstance().equals(addonAPIVersion)))
         {
            String apiVersion = null;
            if (furnaceContainerId != null)
            {
               ArtifactDescriptorResult containerDescriptor = readArtifactDescriptor(furnaceContainerId);
               apiVersion = findDependencyVersion(containerDescriptor.getDependencies(), FURNACE_API_GROUP_ID,
                        FURNACE_API_ARTIFACT_ID);
            }
            else
            {
               apiVersion = resolveAPIVersion(addonId).get();
            }
            if (apiVersion != null)
            {
               builder.setAPIVersion(SingleVersion.valueOf(apiVersion));
            }
         }
      }
      catch (ArtifactDescriptorException e)
      {
         throw new RuntimeException("Error while retrieving addon information for " + addonId, e);
      }
      return new LazyAddonInfo(this, builder);
   }

   @Override
   public Response<File[]> resolveResources(final AddonId addonId)
   {
      RepositorySystem system = container.getRepositorySystem();
      Settings settings = getSettings();
      DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
      final String mavenCoords = toMavenCoords(addonId);
      Artifact queryArtifact = new DefaultArtifact(mavenCoords);
      session.setDependencyTraverser(new AddonDependencyTraverser(classifier));
      session.setDependencySelector(new AddonDependencySelector(classifier));
      Dependency dependency = new Dependency(queryArtifact, null);

      List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);

      CollectRequest collectRequest = new CollectRequest(dependency, repositories);
      DependencyResult result;
      try
      {
         result = system.resolveDependencies(session, new DependencyRequest(collectRequest, null));
      }
      catch (DependencyResolutionException e)
      {
         throw new RuntimeException(e);
      }
      List<Exception> collectExceptions = result.getCollectExceptions();
      Set<File> files = new HashSet<File>();
      List<ArtifactResult> artifactResults = result.getArtifactResults();
      for (ArtifactResult artifactResult : artifactResults)
      {
         Artifact artifact = artifactResult.getArtifact();
         if (isFurnaceAPI(artifact) ||
                  (this.classifier.equals(artifact.getClassifier())
                           && !addonId.getName().equals(artifact.getGroupId() + ":" + artifact.getArtifactId())))
         {
            continue;
         }
         files.add(artifact.getFile());
      }
      return new MavenResponseBuilder<File[]>(files.toArray(new File[files.size()])).setExceptions(collectExceptions);
   }

   @Override
   public Response<AddonId[]> resolveVersions(final String addonName)
   {
      String addonNameSplit;
      String version;

      String[] split = addonName.split(",");
      if (split.length == 2)
      {
         addonNameSplit = split[0];
         version = split[1];
      }
      else
      {
         addonNameSplit = addonName;
         version = null;
      }
      RepositorySystem system = container.getRepositorySystem();
      Settings settings = getSettings();
      DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
      List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
      VersionRangeResult versions = getVersions(system, settings, session, repositories, addonNameSplit, version);
      List<Exception> exceptions = versions.getExceptions();
      List<Version> versionsList = versions.getVersions();
      List<AddonId> addons = new ArrayList<AddonId>();
      List<AddonId> snapshots = new ArrayList<AddonId>();
      for (Version artifactVersion : versionsList)
      {
         AddonId addonId = AddonId.from(addonName, artifactVersion.toString());
         if (Versions.isSnapshot(addonId.getVersion()))
         {
            snapshots.add(addonId);
         }
         else
         {
            addons.add(addonId);
         }
      }
      if (addons.isEmpty())
      {
         addons = snapshots;
      }
      return new MavenResponseBuilder<AddonId[]>(addons.toArray(new AddonId[addons.size()])).setExceptions(exceptions);
   }

   @Override
   public Response<String> resolveAPIVersion(AddonId addonId)
   {
      RepositorySystem system = container.getRepositorySystem();
      Settings settings = getSettings();
      DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
      return resolveAPIVersion(addonId, system, settings, session);
   }

   private Response<String> resolveAPIVersion(AddonId addonId, RepositorySystem system, Settings settings,
            DefaultRepositorySystemSession session)
   {
      List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
      String mavenCoords = toMavenCoords(addonId);
      Artifact queryArtifact = new DefaultArtifact(mavenCoords);

      session.setDependencyTraverser(new AddonDependencyTraverser(classifier));
      session.setDependencySelector(new DependencySelector()
      {

         @Override
         public boolean selectDependency(Dependency dependency)
         {
            Artifact artifact = dependency.getArtifact();
            if (isAddon(artifact))
            {
               return true;
            }
            return isFurnaceAPI(artifact);
         }

         @Override
         public DependencySelector deriveChildSelector(DependencyCollectionContext context)
         {
            return this;
         }
      });
      CollectRequest request = new CollectRequest(new Dependency(queryArtifact, null), repositories);
      CollectResult result;
      try
      {
         result = system.collectDependencies(session, request);
      }
      catch (DependencyCollectionException e)
      {
         throw new RuntimeException(e);
      }
      List<Exception> exceptions = result.getExceptions();
      String apiVersion = findVersion(result.getRoot().getChildren(), FURNACE_API_GROUP_ID, FURNACE_API_ARTIFACT_ID);
      return new MavenResponseBuilder<String>(apiVersion).setExceptions(exceptions);
   }

   private ArtifactDescriptorResult readArtifactDescriptor(AddonId addonId) throws ArtifactDescriptorException
   {
      String coords = toMavenCoords(addonId);
      RepositorySystem system = container.getRepositorySystem();
      Settings settings = getSettings();
      DefaultRepositorySystemSession session = container.setupRepoSession(system, settings);
      List<RemoteRepository> repositories = MavenRepositories.getRemoteRepositories(container, settings);
      ArtifactDescriptorRequest request = new ArtifactDescriptorRequest();
      request.setArtifact(new DefaultArtifact(coords));
      request.setRepositories(repositories);

      ArtifactDescriptorResult result = system.readArtifactDescriptor(session, request);
      return result;
   }

   private String findDependencyVersion(List<Dependency> dependencies, String groupId, String artifactId)
   {
      for (Dependency child : dependencies)
      {
         Artifact childArtifact = child.getArtifact();

         if (groupId.equals(childArtifact.getGroupId())
                  && artifactId.equals(childArtifact.getArtifactId()))
         {
            return childArtifact.getBaseVersion();
         }
      }
      return null;
   }

   private String findVersion(List<DependencyNode> dependencies, String groupId, String artifactId)
   {
      for (DependencyNode child : dependencies)
      {
         Artifact childArtifact = child.getArtifact();

         if (groupId.equals(childArtifact.getGroupId())
                  && artifactId.equals(childArtifact.getArtifactId()))
         {
            return childArtifact.getBaseVersion();
         }
         else
         {
            String version = findVersion(child.getChildren(), groupId, artifactId);
            if (version != null)
            {
               return version;
            }
         }
      }
      return null;
   }

   private VersionRangeResult getVersions(RepositorySystem system, Settings settings, RepositorySystemSession session,
            List<RemoteRepository> repositories,
            String addonName,
            String version)
   {
      try
      {
         String[] split = addonName.split(",");
         if (split.length == 2)
         {
            version = split[1];
         }
         if (version == null || version.isEmpty())
         {
            version = "[,)";
         }
         else if (!version.matches("(\\(|\\[).*?(\\)|\\])"))
         {
            version = "[" + version + "]";
         }

         Artifact artifact = new DefaultArtifact(toMavenCoords(AddonId.from(addonName, version)));
         VersionRangeRequest rangeRequest = new VersionRangeRequest(artifact, repositories, null);
         VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest);
         return rangeResult;
      }
      catch (Exception e)
      {
         throw new RuntimeException("Failed to look up versions for [" + addonName + "]", e);
      }
   }

   private String toMavenCoords(AddonId addonId)
   {
      String coords = addonId.getName() + ":jar:" + this.classifier + ":" + addonId.getVersion();
      return coords;
   }

   private boolean isAddon(Artifact artifact)
   {
      return this.classifier.equals(artifact.getClassifier());
   }

   private AddonId toAddonId(Artifact artifact)
   {
      if (isAddon(artifact))
      {
         return AddonId.from(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact.getBaseVersion());
      }
      throw new IllegalArgumentException("Not a forge-addon: " + artifact);
   }

   private boolean isFurnaceAPI(Artifact artifact)
   {
      return (FURNACE_API_GROUP_ID.equals(artifact.getGroupId()) && FURNACE_API_ARTIFACT_ID.equals(artifact
               .getArtifactId()));
   }

   /**
    * @param scope the scope to be tested upon
    * @return <code>true</code> if the scope indicates an exported dependency
    */
   public static boolean isExported(String scope)
   {
      String artifactScope = Strings.isNullOrEmpty(scope) ? JavaScopes.COMPILE : scope;
      switch (artifactScope)
      {
      case JavaScopes.COMPILE:
      case JavaScopes.RUNTIME:
         return true;
      case JavaScopes.PROVIDED:
      default:
         return false;
      }
   }

   /**
    * Returns if this artifact belongs to a Furnace Container
    */
   public static boolean isFurnaceContainer(Artifact artifact)
   {
      return FURNACE_CONTAINER_GROUP_ID.equals(artifact.getGroupId());
   }

   /**
    * @param settings the settings to set
    */
   public void setSettings(Settings settings)
   {
      this.settings = settings;
   }

   /**
    * @return the settings
    */
   public Settings getSettings()
   {
      return settings == null ? container.getSettings() : settings;
   }

   /**
    * @param resolveAddonAPIVersions the resolveAddonAPIVersions to set
    */
   public void setResolveAddonAPIVersions(boolean resolveAddonAPIVersions)
   {
      this.resolveAddonAPIVersions = resolveAddonAPIVersions;
   }

   /**
    * @return the resolveAddonAPIVersions
    */
   public boolean isResolveAddonAPIVersions()
   {
      return resolveAddonAPIVersions;
   }
} public interface ListenerRegistration<T> extends AutoCloseable
{
   T removeListener();

   @Override
   default void close() throws Exception
   {
      removeListener();
   }
} public class Addons
{

   public static void waitUntilStarted(Addon addon)
   {
      try
      {
         while (!addon.getStatus().isStarted())
         {
            Thread.sleep(10);
         }
      }
      catch (Exception e)
      {
         throw new ContainerException("Addon [" + addon + "]  was not started.", e);
      }
   }

   public static void waitUntilStopped(Addon addon)
   {
      if (addon != null)
      {
         try
         {
            while (addon.getStatus().isStarted())
            {
               Thread.sleep(10);
            }
         }
         catch (Exception e)
         {
            throw new ContainerException("Addon [" + addon + "] was not stopped.", e);
         }
      }
   }

   public static void waitUntilStarted(Addon addon, int quantity, TimeUnit unit) throws TimeoutException
   {
      long start = System.currentTimeMillis();
      long threshold = start + TimeUnit.MILLISECONDS.convert(quantity, unit);
      while (!addon.getStatus().isStarted())
      {
         if (System.currentTimeMillis() > threshold)
         {
            throw new TimeoutException("Timeout expired waiting for [" + addon + "] to start.");
         }

         try
         {
            Thread.sleep(10);
         }
         catch (RuntimeException re)
         {
            throw re;
         }
         catch (Exception e)
         {
            throw new ContainerException("Addon [" + addon + "] was not started.", e);
         }
      }
   }

   public static void waitUntilStopped(Addon addon, int quantity, TimeUnit unit) throws TimeoutException
   {
      if (addon != null)
      {
         long start = System.currentTimeMillis();
         long threshold = start + TimeUnit.MILLISECONDS.convert(quantity, unit);
         while (addon.getStatus().isStarted())
         {
            if (System.currentTimeMillis() > threshold)
            {
               throw new TimeoutException("Timeout expired waiting for [" + addon + "] to stop.");
            }

            try
            {
               Thread.sleep(10);
            }
            catch (RuntimeException re)
            {
               throw re;
            }
            catch (Exception e)
            {
               throw new ContainerException("Addon [" + addon + "] was not stopped.", e);
            }
         }
      }
   }

   /**
    * Waits until the specified {@link Addon} starts or is missing
    */
   public static void waitUntilStartedOrMissing(Addon addon, int quantity, TimeUnit unit) throws TimeoutException
   {
      if (addon != null)
      {
         long start = System.currentTimeMillis();
         long threshold = start + TimeUnit.MILLISECONDS.convert(quantity, unit);
         while (!addon.getStatus().isStarted() && !addon.getStatus().isMissing())
         {
            if (System.currentTimeMillis() > threshold)
            {
               throw new TimeoutException("Timeout expired waiting for [" + addon + "] to load.");
            }

            try
            {
               Thread.sleep(10);
            }
            catch (RuntimeException re)
            {
               throw re;
            }
            catch (Exception e)
            {
               throw new ContainerException("Addon [" + addon + "] was not loaded.", e);
            }
         }
      }
   }

} public class ClassLoaders
{
   private static final Logger log = Logger.getLogger(ClassLoaders.class.getName());

   /**
    * Execute the given {@link Callable} in the {@link ClassLoader} provided. Return the result, if any.
    */
   public static <T> T executeIn(ClassLoader loader, Callable<T> task) throws Exception
   {
      if (task == null)
         return null;

      if (log.isLoggable(Level.FINE))
      {
         log.fine("ClassLoader [" + loader + "] task began.");
      }
      ClassLoader original = SecurityActions.getContextClassLoader();
      try
      {
         SecurityActions.setContextClassLoader(loader);
         return task.call();
      }
      finally
      {
         SecurityActions.setContextClassLoader(original);
         if (log.isLoggable(Level.FINE))
         {
            log.fine("ClassLoader [" + loader + "] task ended.");
         }
      }
   }

   /**
    * Execute the given {@link Runnable} in the {@link ClassLoader} provided. Return the result, if any.
    */
   public static void executeIn(ClassLoader loader, Runnable task) throws Exception
   {
      if (task == null)
         return;

      if (log.isLoggable(Level.FINE))
      {
         log.fine("ClassLoader [" + loader + "] task began.");
      }
      ClassLoader original = SecurityActions.getContextClassLoader();
      try
      {
         SecurityActions.setContextClassLoader(loader);
         task.run();
      }
      finally
      {
         SecurityActions.setContextClassLoader(original);
         if (log.isLoggable(Level.FINE))
         {
            log.fine("ClassLoader [" + loader + "] task ended.");
         }
      }
   }

   /**
    * Execute the given {@link Callable} by creating an {@link URLClassLoader} from the given {@link URL} array.
    * <p/>
    * Return the result, if any.
    */
   public static <T> T executeIn(URL[] urls, Callable<T> callable) throws Exception
   {
      ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader();
      URLClassLoader newClassLoader = null;
      try
      {
         newClassLoader = new URLClassLoader(urls, savedClassLoader);
         return executeIn(newClassLoader, callable);
      }
      finally
      {
         Streams.closeQuietly(newClassLoader);
      }
   }

   public static boolean containsClass(ClassLoader loader, Class<?> type)
   {
      if (loader == null)
         throw new IllegalArgumentException("Class loader to inspect must not be null.");
      if (type == null)
         throw new IllegalArgumentException("Class to find must not be null.");

      try
      {
         return loader.loadClass(type.getName()) == type;
      }
      catch (ClassNotFoundException | LinkageError e)
      {
         return false;
      }
   }

   public static boolean containsClass(ClassLoader loader, String type)
   {
      if (loader == null)
         throw new IllegalArgumentException("Class loader to inspect must not be null.");
      if (type == null)
         throw new IllegalArgumentException("Class to find must not be null.");

      try
      {
         loader.loadClass(type);
         return true;
      }
      catch (ClassNotFoundException | LinkageError e)
      {
         return false;
      }
   }

   public static Class<?> loadClass(ClassLoader loader, String typeName)
   {
      if (loader == null)
         throw new IllegalArgumentException("Class loader to inspect must not be null.");
      if (typeName == null)
         throw new IllegalArgumentException("Class name to find must not be null.");

      try
      {
         return loader.loadClass(typeName);
      }
      catch (ClassNotFoundException | LinkageError e)
      {
         throw new ContainerException("Could not locate class [" + typeName + "] in Loader [" + loader + "]", e);
      }
   }

   public static Class<?> loadClass(ClassLoader loader, Class<?> type)
   {
      if (loader == null)
         throw new IllegalArgumentException("Class loader to inspect must not be null.");
      if (type == null)
         throw new IllegalArgumentException("Class to find must not be null.");

      try
      {
         return loader.loadClass(type.getName());
      }
      catch (ClassNotFoundException | LinkageError e)
      {
         throw new ContainerException("Could not locate class [" + type.getName() + "] in Loader [" + loader + "]", e);
      }
   }

   public static boolean ownsClass(ClassLoader loader, Class<?> type)
   {
      if (loader == null)
         throw new IllegalArgumentException("Class loader to inspect must not be null.");
      if (type == null)
         throw new IllegalArgumentException("Class to find must not be null.");

      return loader.equals(type.getClassLoader());
   }

   public static Throwable getClassLoadingExceptionFor(ClassLoader loader, String typeName)
   {
      try
      {
         loader.loadClass(typeName);
         return null;
      }
      catch (ClassNotFoundException | LinkageError e)
      {
         return e;
      }
   }
} public final class OperatingSystemUtils
{
   private static boolean PRETEND_WINDOWS = Boolean.getBoolean("forge.pretend_windows");

   private static String operatingSystem = null;

   /**
    * Return the name of the host operating system.
    */
   public static String getOsName()
   {
      if (operatingSystem == null)
      {
         operatingSystem = System.getProperty("os.name").toLowerCase();
      }
      return operatingSystem;
   }

   /**
    * Return <code>true</code> if the host environment is Windows.
    */
   public static boolean isWindows()
   {
      return PRETEND_WINDOWS || getOsName().startsWith("windows");
   }

   /**
    * Return <code>true</code> if the host environment is OSX.
    */
   public static boolean isOSX()
   {
      return getOsName().startsWith("mac");
   }

   /**
    * Return <code>true</code> if the host environment is Linux.
    */
   public static boolean isLinux()
   {
      return getOsName().startsWith("linux");
   }

   /**
    * Get the directory from which this application was launched.
    */
   public static File getWorkingDir()
   {
      return new File(System.getProperty("user.dir"));
   }

   /**
    * Get the FORGE_HOME directory as a {@link File}. Returns <code>null</code> if it is not defined.
    */
   public static File getForgeHomeDir()
   {
      String property = System.getProperty("forge.home");
      return property == null ? null : new File(property).getAbsoluteFile();
   }

   /**
    * Get the logged in user's home directory as a {@link File}.
    */
   public static File getUserHomeDir()
   {
      return new File(System.getProperty("user.home")).getAbsoluteFile();
   }

   /**
    * Get the path of the logged in user's home directory.
    */
   public static String getUserHomePath()
   {
      return getUserHomeDir().getAbsolutePath();
   }

   /**
    * Get the logged in user's Furnace directory.
    */
   public static File getUserForgeDir()
   {
      return new File(getUserHomeDir(), ".forge").getAbsoluteFile();
   }

   /**
    * Set this utility to pretend that the current host environment is actually Windows.
    */
   public static void setPretendWindows(boolean value)
   {
      PRETEND_WINDOWS = value;
   }

   /**
    * Return the {@link String} used as line separator for carriage returns.
    */
   public static String getLineSeparator()
   {
      return System.lineSeparator();
   }

   /**
    * Generate a string that is usable as a single file name or directory path segment on any operating system. Replaces
    * unsafe characters with the underscore '_' character
    */
   public static String getSafeFilename(String filename)
   {
      String result = filename;
      if (result != null)
      {
         result = result.replaceAll("[^a-zA-Z0-9]+", "-").replaceAll("^-+", "");
      }
      return result;
   }

   /**
    * Create a temporary directory.
    */
   public static File createTempDir() throws IllegalStateException
   {
      File baseDir = getTempDirectory();
      try
      {
         return Files.createTempDirectory(baseDir.toPath(), "tmpdir").toFile();
      }
      catch (IOException e)
      {
         throw new IllegalStateException("Error while creating temporary directory", e);
      }
   }

   /**
    * Get the current system temp directory.
    */
   public static File getTempDirectory()
   {
      return new File(System.getProperty("java.io.tmpdir"));
   }

   /**
    * @return true if running in JDK 8
    */
   public static boolean isJava8()
   {
      return System.getProperty("java.version", "").startsWith("1.8");
   }

   /**
    * @return true if running in JDK 9
    */
   public static boolean isJava9()
   {
      return System.getProperty("java.version", "").startsWith("9");
   }

} public final class SecurityActions
{
   private static final Logger log = Logger.getLogger(SecurityActions.class.getName());

   private SecurityActions()
   {
      // forbidden inheritance
   }

   /**
    * Gets context classloader.
    *
    * @return the current context classloader
    */
   public static ClassLoader getContextClassLoader()
   {
      if (System.getSecurityManager() == null)
      {
         return Thread.currentThread().getContextClassLoader();
      }
      else
      {
         return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>()
         {
            @Override
            public ClassLoader run()
            {
               return Thread.currentThread().getContextClassLoader();
            }
         });
      }
   }

   /**
    * Sets context classloader.
    *
    * @param classLoader the classloader
    */
   public static void setContextClassLoader(final ClassLoader classLoader)
   {
      if (System.getSecurityManager() == null)
      {
         Thread.currentThread().setContextClassLoader(classLoader);
      }
      else
      {
         AccessController.doPrivileged(new PrivilegedAction<Object>()
         {
            @Override
            public Object run()
            {
               Thread.currentThread().setContextClassLoader(classLoader);
               return null;
            }
         });
      }
   }

   /**
    * Cleanup {@link ThreadLocal} instances of the given {@link Thread}.
    *
    * @param thread The {@link Thread} to clean up.
    */
   public static void cleanupThreadLocals(Thread thread)
   {
      if (!OperatingSystemUtils.isJava8())
      {
         return;
      }
      try
      {
         if (thread != null)
         {
            cleanField(thread, Thread.class.getDeclaredField("threadLocals"));
            cleanField(thread, Thread.class.getDeclaredField("inheritableThreadLocals"));
         }
      }
      catch (Exception e)
      {
         log.log(Level.WARNING, "Failed to cleanup ThreadLocal instances for Thread [" + thread + "]", e);
      }
   }

   private static void cleanField(Thread thread, Field threadLocalsField) throws IllegalAccessException,
            ClassNotFoundException, NoSuchFieldException
   {
      threadLocalsField.setAccessible(true);
      Object threadLocalTable = threadLocalsField.get(thread);

      // Get a reference to the array holding the thread local variables inside the
      // ThreadLocalMap of the current thread
      Class<?> threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
      Field tableField = threadLocalMapClass.getDeclaredField("table");
      tableField.setAccessible(true);
      if (threadLocalTable != null)
      {
         Object table = tableField.get(threadLocalTable);

         // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
         // is a reference to the actual ThreadLocal variable
         Field referentField = Reference.class.getDeclaredField("referent");
         referentField.setAccessible(true);

         for (int i = 0; i < Array.getLength(table); i++)
         {
            // Each entry in the table array of ThreadLocalMap is an Entry object
            // representing the thread local reference and its value
            Object entry = Array.get(table, i);
            if (entry != null)
            {
               // Get a reference to the thread local object and remove it from the table
               ThreadLocal<?> threadLocal = (ThreadLocal<?>) referentField.get(entry);
               if (threadLocal != null)
                  threadLocal.remove();
            }
         }
      }
   }

}
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.container.spi.client.deployment.Deployment;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.forge.arquillian.archive.AddonArchiveBase;
import org.jboss.forge.arquillian.archive.AddonDependencyAware;
import org.jboss.forge.arquillian.archive.AddonDeploymentArchive;
import org.jboss.forge.arquillian.archive.RepositoryLocationAware;
import org.jboss.forge.arquillian.impl.ShrinkWrapUtil;
import org.jboss.forge.arquillian.protocol.FurnaceHolder;
import org.jboss.forge.arquillian.protocol.FurnaceProtocolDescription;
import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.addons.AddonId;
import org.jboss.forge.furnace.addons.AddonRegistry;
import org.jboss.forge.furnace.impl.FurnaceImpl;
import org.jboss.forge.furnace.impl.util.Files;
import org.jboss.forge.furnace.manager.AddonManager;
import org.jboss.forge.furnace.manager.impl.AddonManagerImpl;
import org.jboss.forge.furnace.manager.maven.MavenContainer;
import org.jboss.forge.furnace.manager.maven.addon.MavenAddonDependencyResolver;
import org.jboss.forge.furnace.manager.spi.AddonDependencyResolver;
import org.jboss.forge.furnace.repositories.AddonDependencyEntry;
import org.jboss.forge.furnace.repositories.AddonRepository;
import org.jboss.forge.furnace.repositories.AddonRepositoryMode;
import org.jboss.forge.furnace.repositories.MutableAddonRepository;
import org.jboss.forge.furnace.spi.ContainerLifecycleListener;
import org.jboss.forge.furnace.spi.ListenerRegistration;
import org.jboss.forge.furnace.util.Addons;
import org.jboss.forge.furnace.util.Callables;
import org.jboss.forge.furnace.util.ClassLoaders;
import org.jboss.forge.furnace.util.OperatingSystemUtils;
import org.jboss.forge.furnace.util.SecurityActions;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
   {
      return new FurnaceProtocolDescription();
   }

   @Override
   public void setup(FurnaceContainerConfiguration configuration)
   {
      this.configuration = configuration;
   }

   @Override
   public void start() throws LifecycleException
   {
      try
      {
         this.addonDir = OperatingSystemUtils.createTempDir();
      }
      catch (IllegalStateException e)
      {
         throw new LifecycleException("Failed to create temporary addon directory", e);
      }

      try
      {
         System.out.println("Furnace test harness is initializing with addon dir [" + addonDir + "]");
         initContainer();
         startContainer();
      }
      catch (Exception e)
      {
         throw new LifecycleException("Could not start Furnace runnable.", e);
      }
   }

   private void startContainer()
   {
      waitForConfigurationRescan(new Callable<Void>()
      {
         @Override
         public Void call() throws Exception
         {
            thread.start();
            return null;
         }
      });
   }

   private void initContainer()
   {
      runnable = new ForgeRunnable(ClassLoader.getSystemClassLoader());
      furnaceHolder.setFurnace(runnable.furnace);
      thread = new Thread(runnable, "Arquillian Furnace Runtime");
      repository = (MutableAddonRepository) runnable.furnace.addRepository(AddonRepositoryMode.MUTABLE, addonDir);
   }

   @Override
   public void stop() throws LifecycleException
   {
      stopContainer();
      Files.delete(addonDir, true);
   }

   private void stopContainer()
   {
      this.repository = null;
      this.deployedAddons.clear();
      this.deploymentRepositories.clear();
      this.runnable.stop();
      this.thread = null;
   }

   @Override
   public void undeploy(Archive<?> archive) throws DeploymentException
   {
      undeploying = true;
      AddonId addonToUndeploy = getAddonEntry(deploymentInstance.get());
      AddonRegistry registry = runnable.getForge().getAddonRegistry();
      System.out.println("Furnace test harness is undeploying [" + addonToUndeploy + "] ... ");

      try
      {
         if (archive instanceof AddonDeploymentArchive)
         {
            for (DeploymentListener listener : ((AddonDeploymentArchive) archive).getDeploymentListeners())
            {
               listener.preUndeploy(furnaceHolder.getFurnace(), archive);
            }
         }

         Addon addonToStop = registry.getAddon(addonToUndeploy);
         if (addonToStop.getStatus().isLoaded())
            ((MutableAddonRepository) addonToStop.getRepository()).disable(addonToUndeploy);
         Addons.waitUntilStopped(addonToStop);

         if (archive instanceof AddonDeploymentArchive)
         {
            for (DeploymentListener listener : ((AddonDeploymentArchive) archive).getDeploymentListeners())
            {
               listener.postUndeploy(furnaceHolder.getFurnace(), archive);
            }
         }
      }
      catch (Exception e)
      {
         throw new DeploymentException("Failed to undeploy " + addonToUndeploy, e);
      }
      finally
      {
         repository.undeploy(addonToUndeploy);
      }
   }

   @Override
   public void undeploy(Descriptor descriptor) throws DeploymentException
   {
      throw new UnsupportedOperationException("Descriptors not supported by Furnace");
   }

   private class ForgeRunnable implements Runnable
   {
      private final Furnace furnace;