/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc., Oracle Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
* Oracle Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.extender.internal.support;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.DelegatingEntityResolver;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.eclipse.gemini.blueprint.util.BundleDelegatingClassLoader;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Spring schema handler/resolver for OSGi environments.
*
* Besides delegation this class also does type filtering to avoid wiring the wrong bundle if multiple versions of the
* same library (which support the same schema) are available.
*
* Additionally, lazy handlers are supported so that they are checked (and thus loaded) only if no previous handler has
* been able to satisfy the request.
*
* @author Hal Hildebrand
* @author Costin Leau
*
*/
public class NamespacePlugins implements NamespaceHandlerResolver, EntityResolver, DisposableBean {
/**
* Wrapper class which implements both {@link EntityResolver} and {@link NamespaceHandlerResolver} interfaces.
*
* Simply delegates to the actual implementation discovered in a specific bundle.
*/
private static class Plugin implements NamespaceHandlerResolver, EntityResolver {
private final NamespaceHandlerResolver namespace;
private final EntityResolver entity;
private final Bundle bundle;
private Plugin(Bundle bundle) {
this.bundle = bundle;
ClassLoader loader = BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle);
entity = new DelegatingEntityResolver(loader);
namespace = new DefaultNamespaceHandlerResolver(loader);
}
public NamespaceHandler resolve(String namespaceUri) {
return namespace.resolve(namespaceUri);
}
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return entity.resolveEntity(publicId, systemId);
}
public Bundle getBundle() {
return bundle;
}
}
private static final Log log = LogFactory.getLog(NamespacePlugins.class);
final LazyBundleRegistry.Condition condition = new LazyBundleRegistry.Condition() {
private final String NS_HANDLER_RESOLVER_CLASS_NAME = NamespaceHandlerResolver.class.getName();
public boolean pass(Bundle bundle) {
try {
Class<?> type = bundle.loadClass(NS_HANDLER_RESOLVER_CLASS_NAME);
return NamespaceHandlerResolver.class.equals(type);
} catch (Throwable th) {
// if the interface is not wired, ignore the bundle
log.warn("Bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle) + " cannot see class ["
+ NS_HANDLER_RESOLVER_CLASS_NAME + "]; ignoring it as a namespace resolver");
return false;
}
}
};
private final LazyBundleRegistry.Activator<Plugin> activation = new LazyBundleRegistry.Activator<Plugin>() {
public Plugin activate(Bundle bundle) {
return new Plugin(bundle);
}
};
private final LazyBundleRegistry<Plugin> pluginRegistry =
new LazyBundleRegistry<Plugin>(condition, activation, log);
/**
* Adds a bundle as a handler to plugin registry.
*
* @param bundle
* @param lazyBundle
*/
void addPlugin(Bundle bundle, boolean lazyBundle, boolean applyCondition) {
boolean debug = log.isDebugEnabled();
if (debug)
log.debug("Adding as " + (lazyBundle ? "lazy " : "") + "namespace handler bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(bundle));
pluginRegistry.add(bundle, lazyBundle, applyCondition);
}
/**
* Checks the type compatibility check between the namespace parser wired to Spring DM and the discovered bundle
* class space.
*
* @param bundle handler bundle
* @return true if there is type compatibility, false otherwise
*/
boolean isTypeCompatible(Bundle bundle) {
return condition.pass(bundle);
}
/**
* Returns true if a handler mapping was removed for the given bundle.
*
* @param bundle bundle to look at
* @return true if the bundle was used in the plugin map
*/
boolean removePlugin(Bundle bundle) {
if (log.isDebugEnabled())
log.debug("Removing handler " + OsgiStringUtils.nullSafeNameAndSymName(bundle));
return pluginRegistry.remove(bundle);
}
public NamespaceHandler resolve(final String namespaceUri) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<NamespaceHandler>() {
public NamespaceHandler run() {
return doResolve(namespaceUri);
}
});
} else {
return doResolve(namespaceUri);
}
}
public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
if (System.getSecurityManager() != null) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<InputSource>() {
public InputSource run() throws Exception {
return doResolveEntity(publicId, systemId);
}
});
} catch (PrivilegedActionException pae) {
Exception cause = pae.getException();
handleInputSourceException(cause);
}
} else {
try {
return doResolveEntity(publicId, systemId);
} catch (Exception ex) {
handleInputSourceException(ex);
}
}
return null;
}
private NamespaceHandler doResolve(final String namespaceUri) {
final boolean debug = log.isDebugEnabled();
final boolean trace = log.isTraceEnabled();
if (debug)
log.debug("Trying to resolving namespace handler for " + namespaceUri);
try {
return pluginRegistry.apply(new LazyBundleRegistry.Operation<Plugin, NamespaceHandler>() {
public NamespaceHandler operate(Plugin plugin) {
try {
NamespaceHandler handler = plugin.resolve(namespaceUri);
if (handler != null) {
if (debug)
log.debug("Namespace handler for " + namespaceUri + " found inside bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
return handler;
} else if (trace)
log.trace("Namespace handler for " + namespaceUri + " not found inside bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
} catch (IllegalArgumentException ex) {
if (trace)
log.trace("Namespace handler for " + namespaceUri + " not found inside bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
}
return null;
}
});
} catch (Exception ex) {
// the inner method doesn't declare any exceptions so the cast should be safe
throw (RuntimeException) ex;
}
}
private InputSource doResolveEntity(final String publicId, final String systemId) throws Exception {
final boolean debug = log.isDebugEnabled();
final boolean trace = log.isTraceEnabled();
if (debug)
log.debug("Trying to resolving entity for " + publicId + "|" + systemId);
if (systemId != null) {
return pluginRegistry.apply(new LazyBundleRegistry.Operation<Plugin, InputSource>() {
public InputSource operate(Plugin plugin) throws SAXException, IOException {
try {
InputSource inputSource = plugin.resolveEntity(publicId, systemId);
if (inputSource != null) {
if (debug)
log.debug("XML schema for " + publicId + "|" + systemId + " found inside bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
return inputSource;
}
} catch (FileNotFoundException ex) {
if (trace)
log.trace("XML schema for " + publicId + "|" + systemId + " not found inside bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()), ex);
}
return null;
}
});
}
return null;
}
private void handleInputSourceException(Exception exception) throws SAXException, IOException {
if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
}
if (exception instanceof IOException) {
throw (IOException) exception;
}
throw (SAXException) exception;
}
public void destroy() {
pluginRegistry.clear();
}
}