blob: 25b961d9e7991e2a17b3dc88bd72d880aab47d30 [file] [log] [blame]
/******************************************************************************
* 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.context.support;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.eclipse.gemini.blueprint.io.OsgiBundleResource;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.eclipse.gemini.blueprint.util.internal.BundleUtils;
import org.osgi.framework.BundleContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.DelegatingEntityResolver;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.xml.sax.EntityResolver;
/**
* Stand-alone XML application context, backed by an OSGi bundle.
*
* <p> The configuration location defaults can be overridden via {@link #getDefaultConfigLocations()}. Note that
* locations can either denote concrete files like <code>/myfiles/context.xml</code> or <em>Ant-style</em> patterns like
* <code>/myfiles/*-context.xml</code> (see the {@link org.springframework.util.AntPathMatcher} javadoc for pattern
* details). </p>
*
* <p> <strong>Note:</strong> In case of multiple configuration locations, later bean definitions will override ones
* defined in earlier loaded files. This can be leveraged to deliberately override certain bean definitions via an extra
* XML file. </p>
*
* <p/> <b>This is the main ApplicationContext class for OSGi environments.</b>
*
* @author Adrian Colyer
* @author Costin Leau
* @author Andy Piper
* @author Hal Hildebrand
*/
public class OsgiBundleXmlApplicationContext extends AbstractDelegatedExecutionApplicationContext implements DisposableBean {
/** Default config location for the root context(s) */
public static final String DEFAULT_CONFIG_LOCATION =
OsgiBundleResource.BUNDLE_URL_PREFIX + "/META-INF/spring/*.xml";
/**
*
* Creates a new <code>OsgiBundleXmlApplicationContext</code> with no parent.
*
*/
public OsgiBundleXmlApplicationContext() {
this((String[]) null);
}
/**
* Creates a new <code>OsgiBundleXmlApplicationContext</code> with the given parent context.
*
* @param parent the parent context
*/
public OsgiBundleXmlApplicationContext(ApplicationContext parent) {
this(null, parent);
}
/**
* Creates a new <code>OsgiBundleXmlApplicationContext</code> with the given configLocations.
*
* @param configLocations array of configuration resources
*/
public OsgiBundleXmlApplicationContext(String[] configLocations) {
this(configLocations, null);
}
/**
* Creates a new <code>OsgiBundleXmlApplicationContext</code> with the given configLocations and parent context.
*
* @param configLocations array of configuration resources
* @param parent the parent context
*/
public OsgiBundleXmlApplicationContext(String[] configLocations, ApplicationContext parent) {
super(parent);
setConfigLocations(configLocations);
}
/**
* {@inheritDoc}
*
* <p/> Loads the bean definitions via an <code>XmlBeanDefinitionReader</code>.
*/
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with the context
// resource loading environment.
beanDefinitionReader.setResourceLoader(this);
// add a specialized DocumentLoader to load blueprint configs w/o a schema location
beanDefinitionReader.setDocumentLoader(new BlueprintDocumentLoader());
final Object[] resolvers = new Object[2];
final BundleContext ctx = getBundleContext();
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
String filter = BundleUtils.createNamespaceFilter(ctx);
resolvers[0] = createNamespaceHandlerResolver(ctx, filter, getClassLoader());
resolvers[1] = createEntityResolver(ctx, filter, getClassLoader());
return null;
}
});
} else {
String filter = BundleUtils.createNamespaceFilter(ctx);
resolvers[0] = createNamespaceHandlerResolver(ctx, filter, getClassLoader());
resolvers[1] = createEntityResolver(ctx, filter, getClassLoader());
}
beanDefinitionReader.setNamespaceHandlerResolver((NamespaceHandlerResolver) resolvers[0]);
beanDefinitionReader.setEntityResolver((EntityResolver) resolvers[1]);
// Allow a subclass to provide custom initialisation of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
/**
* Allows subclasses to do custom initialisation here.
*
* @param beanDefinitionReader
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* Loads the bean definitions with the given <code>XmlBeanDefinitionReader</code>.
*
* <p> The lifecycle of the bean factory is handled by the refreshBeanFactory method; therefore this method is just
* supposed to load and/or register bean definitions.
*
* <p> Delegates to a ResourcePatternResolver for resolving location patterns into Resource instances.
*
* @throws org.springframework.beans.BeansException in case of bean registration errors
* @throws java.io.IOException if the required XML document isn't found
* @see #refreshBeanFactory
* @see #getConfigLocations
* @see #getResources
* @see #getResourcePatternResolver
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
String[] configLocations = expandLocations(getConfigLocations());
if (configLocations != null) {
for (int i = 0; i < configLocations.length; i++) {
reader.loadBeanDefinitions(configLocations[i]);
}
}
}
/**
* Expands any folder entries supplied as configuration location. I.e. config/ becomes config/*.xml.
*
* @param configLocations
* @return
*/
private String[] expandLocations(String[] configLocations) {
String[] expanded = null;
if (configLocations != null) {
expanded = new String[configLocations.length];
for (int i = 0; i < configLocations.length; i++) {
String location = configLocations[i];
if (location.endsWith("/")) {
location = location + "*.xml";
}
expanded[i] = location;
}
}
return expanded;
}
/**
* Provide default locations for XML files. This implementation returns <code>META-INF/spring/*.xml</code> relying
* on the default resource environment for actual localisation. By default, the bundle space will be used for
* locating the resources.
*
* <p/> <strong>Note:</strong> Instead of overriding this method, consider using the Spring-DM specific header
* inside your manifest bundle.
*
* @return default XML configuration locations
*/
protected String[] getDefaultConfigLocations() {
return new String[] { DEFAULT_CONFIG_LOCATION };
}
/**
* Creates a special OSGi namespace handler resolver that first searches the bundle class path falling back to the
* namespace service published by Spring-DM. This allows embedded libraries that provide namespace handlers take
* priority over namespace provided by other bundles.
*
* @param bundleContext the OSGi context of which the resolver should be aware of
* @param filter OSGi service filter
* @param bundleClassLoader classloader for creating the OSGi namespace resolver proxy
* @return a OSGi aware namespace handler resolver
*/
private NamespaceHandlerResolver createNamespaceHandlerResolver(BundleContext bundleContext, String filter,
ClassLoader bundleClassLoader) {
Assert.notNull(bundleContext, "bundleContext is required");
// create local namespace resolver
// we'll use the default resolver which uses the bundle local class-loader
NamespaceHandlerResolver localNamespaceResolver = new DefaultNamespaceHandlerResolver(bundleClassLoader);
// hook in OSGi namespace resolver
NamespaceHandlerResolver osgiServiceNamespaceResolver =
lookupNamespaceHandlerResolver(bundleContext, filter, localNamespaceResolver);
DelegatedNamespaceHandlerResolver delegate = new DelegatedNamespaceHandlerResolver();
delegate.addNamespaceHandler(localNamespaceResolver, "LocalNamespaceResolver for bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(bundleContext.getBundle()));
delegate.addNamespaceHandler(osgiServiceNamespaceResolver, "OSGi Service resolver");
return delegate;
}
/**
* Similar to {@link #createNamespaceHandlerResolver(BundleContext, String, ClassLoader)} , this method creates
* a special OSGi entity resolver that considers the bundle class path first, falling back to the entity resolver
* service provided by the Spring DM extender.
*
* @param bundleContext the OSGi context of which the resolver should be aware of
* @param filter OSGi service filter
* @param bundleClassLoader classloader for creating the OSGi namespace resolver proxy
* @return a OSGi aware entity resolver
*/
private EntityResolver createEntityResolver(BundleContext bundleContext, String filter,
ClassLoader bundleClassLoader) {
Assert.notNull(bundleContext, "bundleContext is required");
// create local namespace resolver
EntityResolver localEntityResolver = new DelegatingEntityResolver(bundleClassLoader);
// hook in OSGi namespace resolver
EntityResolver osgiServiceEntityResolver = lookupEntityResolver(bundleContext, filter, localEntityResolver);
ChainedEntityResolver delegate = new ChainedEntityResolver();
delegate.addEntityResolver(localEntityResolver, "LocalEntityResolver for bundle "
+ OsgiStringUtils.nullSafeNameAndSymName(bundleContext.getBundle()));
// hook in OSGi namespace resolver
delegate.addEntityResolver(osgiServiceEntityResolver, "OSGi Service resolver");
return delegate;
}
private NamespaceHandlerResolver lookupNamespaceHandlerResolver(final BundleContext bundleContext, String filter,
final Object fallbackObject) {
return (NamespaceHandlerResolver) TrackingUtil.getService(new Class<?>[] { NamespaceHandlerResolver.class },
filter, NamespaceHandlerResolver.class.getClassLoader(), bundleContext, fallbackObject);
}
private EntityResolver lookupEntityResolver(final BundleContext bundleContext, String filter,
final Object fallbackObject) {
return (EntityResolver) TrackingUtil.getService(new Class<?>[] { EntityResolver.class }, filter,
EntityResolver.class.getClassLoader(), bundleContext, fallbackObject);
}
public String[] getConfigLocations() {
return super.getConfigLocations();
}
}