blob: 31ca19a84f60d70dc74dacfb0f4f6e474f07e53f [file] [log] [blame]
/**
* 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.annotation.service.BlueprintAnnotationScanner;
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);
}
}
// Check annotations
if (blueprintHeaderAnnotation != null && blueprintHeaderAnnotation.trim().equalsIgnoreCase("true")) {
LOGGER.debug("Scanning bundle {}/{} for blueprint annotations", bundle.getSymbolicName(), bundle.getVersion());
ServiceReference sr = this.context.getServiceReference(BlueprintAnnotationScanner.class.getName());
if (sr != null) {
BlueprintAnnotationScanner bas = (BlueprintAnnotationScanner) this.context.getService(sr);
try {
// try to generate the blueprint definition XML
URL url = bas.createBlueprintModel(bundle);
if (url != null) {
pathList.add(url);
}
} finally {
this.context.ungetService(sr);
}
}
}
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);
}
}
}