content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.aries.blueprint.container;
  
  import java.io.File;
  import java.net.MalformedURLException;
  import java.net.URI;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.FutureTask;
  import java.util.concurrent.ScheduledExecutorService;
  
  import org.apache.aries.blueprint.BlueprintConstants;
  import org.apache.aries.blueprint.namespace.NamespaceHandlerRegistryImpl;
  import org.apache.aries.blueprint.services.BlueprintExtenderService;
  import org.apache.aries.blueprint.services.ParserService;
  import org.apache.aries.blueprint.utils.HeaderParser;
  import org.apache.aries.blueprint.utils.HeaderParser.PathElement;
  import org.apache.aries.blueprint.utils.ServiceUtil;
  import org.apache.aries.blueprint.utils.threading.ScheduledExecutorServiceWrapper;
  import org.apache.aries.blueprint.utils.threading.ScheduledExecutorServiceWrapper.ScheduledExecutorServiceFactory;
  import org.apache.aries.proxy.ProxyManager;
  import org.apache.aries.util.tracker.RecursiveBundleTracker;
  import org.apache.aries.util.tracker.SingleServiceTracker;
  import org.apache.aries.util.tracker.SingleServiceTracker.SingleServiceListener;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.BundleActivator;
  import org.osgi.framework.BundleContext;
  import org.osgi.framework.BundleEvent;
  import org.osgi.framework.Constants;
  import org.osgi.framework.ServiceReference;
  import org.osgi.framework.ServiceRegistration;
  import org.osgi.framework.SynchronousBundleListener;
  import org.osgi.service.blueprint.container.BlueprintContainer;
  import org.osgi.service.blueprint.container.BlueprintEvent;
  import org.osgi.util.tracker.BundleTracker;
  import org.osgi.util.tracker.BundleTrackerCustomizer;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * This is the blueprint extender that listens to blueprint bundles.  
   *
   * @version $Rev$, $Date$
   */
  public class BlueprintExtender implements BundleActivator, BundleTrackerCustomizer, SynchronousBundleListener {
  
      /** The QuiesceParticipant implementation class name */
      private static final String CONTAINER_ADDITIONAL_NAMESPACES = "org.apache.aries.blueprint.extender.additional.namespaces";
      private static final String QUIESCE_PARTICIPANT_CLASS = "org.apache.aries.quiesce.participant.QuiesceParticipant";
      private static final String EXTENDER_THREADS_PROPERTY = "org.apache.aries.blueprint.extender.threads";
      private static final int DEFAULT_NUMBER_OF_THREADS = 3;
      private static final Logger LOGGER = LoggerFactory.getLogger(BlueprintExtender.class);
  
      private BundleContext context;
      private ScheduledExecutorService executors;
      private final ConcurrentMap<Bundle, BlueprintContainerImpl> containers = new ConcurrentHashMap<Bundle, BlueprintContainerImpl>();
      private final ConcurrentMap<Bundle, FutureTask> destroying = new ConcurrentHashMap<Bundle, FutureTask>();
      private BlueprintEventDispatcher eventDispatcher;
      private NamespaceHandlerRegistry handlers;
      private Object bt;
      private ServiceRegistration parserServiceReg;
      private ServiceRegistration blueprintServiceReg;
      private ServiceRegistration quiesceParticipantReg;
      private SingleServiceTracker<ProxyManager> proxyManager;
      private ExecutorServiceFinder executorServiceFinder;
      private volatile boolean stopping;
  
      public void start(BundleContext ctx) {
          LOGGER.debug("Starting blueprint extender...");
  
          this.context = ctx;
          boolean useSystemContext = Boolean.parseBoolean(ctx.getProperty(BlueprintConstants.USE_SYSTEM_CONTEXT_PROPERTY));
          BundleContext trackingContext = useSystemContext ? ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext() : ctx;
  
          handlers = new NamespaceHandlerRegistryImpl(trackingContext);
          executors = new ScheduledExecutorServiceWrapper(ctx, "Blueprint Extender", new ScheduledExecutorServiceFactory() {
              public ScheduledExecutorService create(String name) {
                  int extenderThreads = DEFAULT_NUMBER_OF_THREADS;
                  try {
                      extenderThreads = Integer.getInteger(EXTENDER_THREADS_PROPERTY, DEFAULT_NUMBER_OF_THREADS);
                      if (extenderThreads != DEFAULT_NUMBER_OF_THREADS) {
                          LOGGER.debug(EXTENDER_THREADS_PROPERTY + " is set to " + extenderThreads + ".");
                      }
                  }
                  catch (Exception e) {
                      LOGGER.error(EXTENDER_THREADS_PROPERTY + " is not a number. Using default value " + DEFAULT_NUMBER_OF_THREADS + ".");
                  }
                  return Executors.newScheduledThreadPool(extenderThreads, new BlueprintThreadFactory(name));
              }
          });
          eventDispatcher = new BlueprintEventDispatcher(ctx);
  
          // Ideally we'd want to only track STARTING and ACTIVE bundle, but this is not supported
          // when using equinox composites.  This would ensure that no STOPPING event is lost while
          // tracking the initial bundles. To work around this issue, we need to register
          // a synchronous bundle listener that will ensure the stopping event will be correctly
          // handled.
          context.addBundleListener(this);
          int mask = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE;
          
          bt = useSystemContext ? new BundleTracker(trackingContext, mask, this) : new RecursiveBundleTracker(ctx, mask, this);
          proxyManager = new SingleServiceTracker<ProxyManager>(ctx, ProxyManager.class, new SingleServiceListener() {
              public void serviceFound() {
                  LOGGER.debug("Found ProxyManager service, starting to process blueprint bundles");
                  if (bt instanceof BundleTracker) {
                      ((BundleTracker) bt).open();
                  } else if (bt instanceof RecursiveBundleTracker) {
                      ((RecursiveBundleTracker) bt).open();
                  }
              }
  
              public void serviceLost() {
                  while (!containers.isEmpty()) {
                      for (Bundle bundle : getBundlesToDestroy()) {
                          destroyContainer(bundle);
                      }
                  }
                  if (bt instanceof BundleTracker) {
                      ((BundleTracker) bt).close();
                  } else if (bt instanceof RecursiveBundleTracker) {
                      ((RecursiveBundleTracker) bt).close();
                  }
              }
  
              public void serviceReplaced() {
              }
          });
          proxyManager.open();
          
          // Determine if the ParserService should ignore unknown namespace handlers
          boolean ignoreUnknownNamespaceHandlers = Boolean.parseBoolean(ctx.getProperty(BlueprintConstants.IGNORE_UNKNOWN_NAMESPACE_HANDLERS_PROPERTY));
          // Create and publish a ParserService
          parserServiceReg = ctx.registerService(ParserService.class.getName(),
                  new ParserServiceImpl(handlers, ignoreUnknownNamespaceHandlers),
                  new Hashtable<String, Object>());
  
          // Create and publish a BlueprintContainerService
          blueprintServiceReg = ctx.registerService(
                  BlueprintExtenderService.class.getName(),
                  new BlueprintContainerServiceImpl(),
                  new Hashtable<String, Object>());
  
          try {
              ctx.getBundle().loadClass(QUIESCE_PARTICIPANT_CLASS);
              //Class was loaded, register
  
              quiesceParticipantReg = ctx.registerService(QUIESCE_PARTICIPANT_CLASS,
                      new BlueprintQuiesceParticipant(ctx, this),
                      new Hashtable<String, Object>());
          } catch (ClassNotFoundException e) {
              LOGGER.info("No quiesce support is available, so blueprint components will not participate in quiesce operations");
          }
          
          LOGGER.debug("Blueprint extender started");
      }
  
      public void stop(BundleContext context) {
          LOGGER.debug("Stopping blueprint extender...");
  
          stopping = true;
  
          ServiceUtil.safeUnregisterService(parserServiceReg);
          ServiceUtil.safeUnregisterService(blueprintServiceReg);
          ServiceUtil.safeUnregisterService(quiesceParticipantReg);
  
          // Orderly shutdown of containers
          while (!containers.isEmpty()) {
              for (Bundle bundle : getBundlesToDestroy()) {
                  destroyContainer(bundle);
              }
          }
  
          if (bt instanceof BundleTracker) {
              ((BundleTracker) bt).close();
          } else if (bt instanceof RecursiveBundleTracker) {
              ((RecursiveBundleTracker) bt).close();
          }
          proxyManager.close();
  
          this.eventDispatcher.destroy();
          this.handlers.destroy();
          executors.shutdown();
          LOGGER.debug("Blueprint extender stopped");
      }
  
      /*
       * SynchronousBundleListener
       */
  
      public void bundleChanged(BundleEvent event) {
          Bundle bundle = event.getBundle();
          if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) {
              // The bundle is not in STARTING or ACTIVE state anymore
              // so destroy the context.  Ignore our own bundle since it
              // needs to kick the orderly shutdown and not unregister the namespaces.
              if (bundle != this.context.getBundle()) {
                  destroyContainer(bundle);
              }
          }
      }
  
      /*
       * BundleTrackerCustomizer
       */
  
      public Object addingBundle(Bundle bundle, BundleEvent event) {
          modifiedBundle(bundle, event, bundle);
          return bundle;
      }
  
      public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
          // If the bundle being stopped is the system bundle,
          // do an orderly shutdown of all blueprint contexts now
          // so that service usage can actually be useful
          if (context.getBundle(0).equals(bundle) && bundle.getState() == Bundle.STOPPING) {
              String val = context.getProperty(BlueprintConstants.PREEMPTIVE_SHUTDOWN_PROPERTY);
              if (val == null || Boolean.parseBoolean(val)) {
                  stop(context);
                  return;
              }
          }
          if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) {
              // The bundle is not in STARTING or ACTIVE state anymore
              // so destroy the context.  Ignore our own bundle since it
              // needs to kick the orderly shutdown and not unregister the namespaces.
              if (bundle != this.context.getBundle()) {
                  destroyContainer(bundle);
              }
              return;
          }
          // Do not track bundles given we are stopping
          if (stopping) {
              return;
          }
          // For starting bundles, ensure, it's a lazy activation,
          // else we'll wait for the bundle to become ACTIVE
          if (bundle.getState() == Bundle.STARTING) {
              String activationPolicyHeader = (String) bundle.getHeaders().get(Constants.BUNDLE_ACTIVATIONPOLICY);
              if (activationPolicyHeader == null || !activationPolicyHeader.startsWith(Constants.ACTIVATION_LAZY)) {
                  // Do not track this bundle yet
                  return;
              }
          }
          createContainer(bundle);
      }
  
      public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
          // Nothing to do
          destroyContainer(bundle);
      }
  
      private boolean createContainer(Bundle bundle) {
          List<URL> paths = getBlueprintPaths(bundle);
          return createContainer(bundle, paths);
      }
  
      private boolean createContainer(Bundle bundle, List<URL> paths) {
          return createContainer(bundle, paths, getAdditionalNamespaces(bundle));
      }
      
      private Collection<URI> getAdditionalNamespaces(Bundle bundle) {
          String additionalNamespacesProperty = bundle.getBundleContext().getProperty(CONTAINER_ADDITIONAL_NAMESPACES);
          Set<URI> uris = null;
          if(additionalNamespacesProperty != null) {
              uris = new HashSet<URI>();
              String[] strings = additionalNamespacesProperty.split(",");
              for (String str : strings) {
                  str = str.trim();
                  if (str.length() != 0) {
                      uris.add(URI.create(str));
                  }
              }
          }
          return uris;
      }
  
      private boolean createContainer(Bundle bundle, List<URL> paths, Collection<URI> namespaces) {
          try {
              if (paths == null || paths.isEmpty()) {
                  // This bundle is not a blueprint bundle, so ignore it
                  return false;
              }
              ProxyManager pm = proxyManager.getService();
              if (pm == null) {
                  // The pm isn't available.  It may be because it is being untracked
                  return false;
              }
              BundleContext bundleContext = bundle.getBundleContext();
              if (bundleContext == null) {
                  // The bundle has been stopped in the mean time
                  return false;
              }
              BlueprintContainerImpl blueprintContainer = new BlueprintContainerImpl(bundle, bundleContext,
                                                                  context.getBundle(), eventDispatcher,
                                                                  handlers, getExecutorService(bundle),
                                                                  executors, paths, pm, namespaces);
              synchronized (containers) {
                  if (containers.putIfAbsent(bundle, blueprintContainer) != null) {
                      return false;
                  }
              }
              String val = context.getProperty(BlueprintConstants.SYNCHRONOUS_PROPERTY);
              if (Boolean.parseBoolean(val)) {
                  LOGGER.debug("Starting creation of blueprint bundle {}/{} synchronously", bundle.getSymbolicName(), bundle.getVersion());
                  blueprintContainer.run();
              } else {
                  LOGGER.debug("Scheduling creation of blueprint bundle {}/{} asynchronously", bundle.getSymbolicName(), bundle.getVersion());
                  blueprintContainer.schedule();
              }
              return true;
          } catch (Throwable t) {
              LOGGER.warn("Error while creating container for blueprint bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion(), t);
              return false;
          }
      }
  
      private void destroyContainer(final Bundle bundle) {
          FutureTask future;
          synchronized (containers) {
              LOGGER.debug("Starting container destruction process for blueprint bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion());
              future = destroying.get(bundle);
              if (future == null) {
                  final BlueprintContainerImpl blueprintContainer = containers.remove(bundle);
                  if (blueprintContainer != null) {
                      LOGGER.debug("Scheduling container destruction for blueprint bundle {}/{}.", bundle.getSymbolicName(), bundle.getVersion());
                      future = new FutureTask<Void>(new Runnable() {
                          public void run() {
                              LOGGER.info("Destroying container for blueprint bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion());
                              try {
                                  blueprintContainer.destroy();
                              } finally {
                                  LOGGER.debug("Finished destroying container for blueprint bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion());
                                  eventDispatcher.removeBlueprintBundle(bundle);
                                  synchronized (containers) {
                                      destroying.remove(bundle);
                                  }
                              }
                          }
                      }, null);
                      destroying.put(bundle, future);
                  } else {
                      LOGGER.debug("Not a blueprint bundle or destruction of container already finished for {}/{}.", bundle.getSymbolicName(), bundle.getVersion());
                  }
              } else {
                  LOGGER.debug("Destruction already scheduled for blueprint bundle {}/{}.", bundle.getSymbolicName(), bundle.getVersion());
              }
          }
          if (future != null) {
              try {
                  LOGGER.debug("Waiting for container destruction for blueprint bundle {}/{}.", bundle.getSymbolicName(), bundle.getVersion());
                  future.run();
                  future.get();
              } catch (Throwable t) {
                  LOGGER.warn("Error while destroying container for blueprint bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion(), t);
              }
          }
      }
  
      private List<URL> resolvePaths(Bundle bundle, List<Object> paths) {
          if (paths == null || paths.isEmpty()) {
              return null;
          }
          List<URL> resolved = new ArrayList<URL>(paths.size());
          for (Object path : paths) {
              if (path instanceof URL) {
                  resolved.add((URL) path);
              } else if (path instanceof String) {
                  String name = (String) path;
                  if (name.endsWith("/")) {
                      addEntries(bundle, name, "*.xml", resolved);
                  } else {
                      String baseName;
                      String filePattern;
                      int pos = name.lastIndexOf('/');
                      if (pos < 0) {
                          baseName = "/";
                          filePattern = name;
                      } else {
                          baseName = name.substring(0, pos + 1);
                          filePattern = name.substring(pos + 1);
                      }
                      addEntries(bundle, baseName, filePattern, resolved);
                  }
              } else {
                  throw new IllegalArgumentException("Unexpected path type: " + path.getClass());
              }
          }
          return resolved;
      }
  
      private List<URL> getBlueprintPaths(Bundle bundle) {
          LOGGER.debug("Scanning bundle {}/{} for blueprint application", bundle.getSymbolicName(), bundle.getVersion());
          try {
              List<URL> pathList = new ArrayList<URL>();
              String blueprintHeader = bundle.getHeaders().get(BlueprintConstants.BUNDLE_BLUEPRINT_HEADER);
              String blueprintHeaderAnnotation = bundle.getHeaders().get(BlueprintConstants.BUNDLE_BLUEPRINT_ANNOTATION_HEADER);
              if (blueprintHeader == null) {
                  blueprintHeader = "OSGI-INF/blueprint/";
              }
              List<PathElement> paths = HeaderParser.parseHeader(blueprintHeader);
              for (PathElement path : paths) {
                  String name = path.getName();
                  if (name.endsWith("/")) {
                      addEntries(bundle, name, "*.xml", pathList);
                  } else {
                      String baseName;
                      String filePattern;
                      int pos = name.lastIndexOf('/');
                      if (pos < 0) {
                          baseName = "/";
                          filePattern = name;
                      } else {
                          baseName = name.substring(0, pos + 1);
                          filePattern = name.substring(pos + 1);
                      }
                      addEntries(bundle, baseName, filePattern, pathList);
                  }
              }
              if (!pathList.isEmpty()) {
                  LOGGER.debug("Found blueprint application in bundle {}/{} with paths: {}", bundle.getSymbolicName(), bundle.getVersion(), pathList);
                  // Check compatibility
                  // TODO: For lazy bundles, the class is either loaded from an imported package or not found, so it should
                  // not trigger the activation.  If it does, we need to use something else like package admin or
                  // ServiceReference, or just not do this check, which could be quite harmful.
                  if (isCompatible(bundle)) {
                      return pathList;
                  } else {
                      LOGGER.info("Bundle {}/{} is not compatible with this blueprint extender", bundle.getSymbolicName(), bundle.getVersion());
                  }
              } else {
                  LOGGER.debug("No blueprint application found in bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion());
              }
          } catch (Throwable t) {
              if (!stopping) {
                  LOGGER.warn("Error creating blueprint container for bundle {}/{}", bundle.getSymbolicName(), bundle.getVersion(), t);
                  eventDispatcher.blueprintEvent(new BlueprintEvent(BlueprintEvent.FAILURE, bundle, context.getBundle(), t));
              }
          }
          return null;
      }
  
      private List<Bundle> getBundlesToDestroy() {
          List<Bundle> bundlesToDestroy = new ArrayList<Bundle>();
          for (Bundle bundle : containers.keySet()) {
              ServiceReference[] references = bundle.getRegisteredServices();
              int usage = 0;
              if (references != null) {
                  for (ServiceReference reference : references) {
                      usage += getServiceUsage(reference);
                  }
              }
              LOGGER.debug("Usage for bundle {}/{} is {}", bundle.getSymbolicName(), bundle.getVersion(), usage);
              if (usage == 0) {
                  bundlesToDestroy.add(bundle);
              }
          }
          if (!bundlesToDestroy.isEmpty()) {
              Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
                  public int compare(Bundle b1, Bundle b2) {
                      return (int) (b2.getLastModified() - b1.getLastModified());
                  }
              });
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug("Selected bundles {} for destroy (no services in use)", toString(bundlesToDestroy));
              }
          } else {
              ServiceReference ref = null;
              for (Bundle bundle : containers.keySet()) {
                  ServiceReference[] references = bundle.getRegisteredServices();
                  for (ServiceReference reference : references) {
                      if (getServiceUsage(reference) == 0) {
                          continue;
                      }
                      if (ref == null || reference.compareTo(ref) < 0) {
                          LOGGER.debug("Currently selecting bundle {}/{} for destroy (with reference {})", bundle.getSymbolicName(), bundle.getVersion(), reference);
                          ref = reference;
                      }
                  }
              }
              if (ref != null) {
                  bundlesToDestroy.add(ref.getBundle());
              }
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug("Selected bundles {} for destroy (lowest ranking service)", toString(bundlesToDestroy));
              }
          }
          return bundlesToDestroy;
      }
  
      private static String toString(List<Bundle> bundles) {
          StringBuilder sb = new StringBuilder();
          sb.append("[");
          for (int i = 0; i < bundles.size(); i++) {
              if (i > 0) {
                  sb.append(", ");
              }
              sb.append(bundles.get(i).getSymbolicName());
              sb.append("/");
              sb.append(bundles.get(i).getVersion());
          }
          sb.append("]");
          return sb.toString();
      }
  
      private static int getServiceUsage(ServiceReference ref) {
          Bundle[] usingBundles = ref.getUsingBundles();
          return (usingBundles != null) ? usingBundles.length : 0;
      }
  
      private ExecutorService getExecutorService(Bundle bundle) {
          if (executorServiceFinder != null) {
              return executorServiceFinder.find(bundle);
          } else {
              return executors;
          }
      }
  
      interface ExecutorServiceFinder {
  
          ExecutorService find(Bundle bundle);
  
      }
  
      private boolean isCompatible(Bundle bundle) {
          // Check compatibility
          boolean compatible;
          if (bundle.getState() == Bundle.ACTIVE) {
              try {
                  Class<?> clazz = bundle.getBundleContext().getBundle().loadClass(BlueprintContainer.class.getName());
                  compatible = (clazz == BlueprintContainer.class);
              } catch (ClassNotFoundException e) {
                  compatible = true;
              }
          } else {
              // for lazy bundle, we can't load the class, so just assume it's ok
              compatible = true;
          }
          return compatible;
      }
  
      private String getFilePart(URL url) {
          String path = url.getPath();
          int index = path.lastIndexOf('/');
          return path.substring(index + 1);
      }
      
      private String cachePath(Bundle bundle, String filePath) {
          return Integer.toHexString(bundle.hashCode()) + "/" + filePath;
      }    
      
      private URL getOverrideURLForCachePath(String privatePath) {
          URL override = null;
          File privateDataVersion = context.getDataFile(privatePath);
          if (privateDataVersion != null
                  && privateDataVersion.exists()) {
              try {
                  override = privateDataVersion.toURI().toURL();
              } catch (MalformedURLException e) {
                  LOGGER.error("Unexpected URL Conversion Issue", e);
              }
          }
          return override;
      }
  
      private URL getOverrideURL(Bundle bundle, URL path, String basePath) {
          String cachePath = cachePath(bundle, basePath + getFilePart(path));
          return getOverrideURLForCachePath(cachePath);
      }    
      
      private void addEntries(Bundle bundle, String path, String filePattern, List<URL> pathList) {
          Enumeration<?> e = bundle.findEntries(path, filePattern, false);
          while (e != null && e.hasMoreElements()) {
              URL u = (URL) e.nextElement();
              URL override = getOverrideURL(bundle, u, path);
              if (override == null) {
                  pathList.add(u);
              } else {
                  pathList.add(override);
              }
          }
      }
  
      protected BlueprintContainerImpl getBlueprintContainerImpl(Bundle bundle) {
          return containers.get(bundle);
      }
  
      private class BlueprintContainerServiceImpl implements BlueprintExtenderService {
  
          public BlueprintContainer createContainer(Bundle bundle) {
              if (BlueprintExtender.this.createContainer(bundle)) {
                  return getContainer(bundle);
              } else {
                  return null;
              }
          }
  
          public BlueprintContainer createContainer(Bundle bundle, List<Object> blueprintPaths) {
              if (BlueprintExtender.this.createContainer(bundle, resolvePaths(bundle, blueprintPaths))) {
                  return getContainer(bundle);
              } else {
                  return null;
              }
          }
  
          public BlueprintContainer createContainer(Bundle bundle, List<Object> blueprintPaths, Collection<URI> namespaces) {
              if (BlueprintExtender.this.createContainer(bundle, resolvePaths(bundle, blueprintPaths), namespaces)) {
                  return getContainer(bundle);
              } else {
                  return null;
              }
          }
  
          public void destroyContainer(Bundle bundle, BlueprintContainer container) {
              BlueprintContainer bundleContainer = getContainer(bundle);
              if (bundleContainer != container) {
                  String error = "Unexpected Blueprint Container";
                  LOGGER.error(error);
                  throw new IllegalArgumentException(error);
              }
              BlueprintExtender.this.destroyContainer(bundle);
          }
  
          public BlueprintContainer getContainer(Bundle bundle) {
              return BlueprintExtender.this.getBlueprintContainerImpl(bundle);
          }
  
      }
      
  }
  