/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed 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.
*
* Created on 23-Jan-2006 by Adrian Colyer
*/
package org.springframework.osgi.context.support;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Properties;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.osgi.context.AbstractRefreshableOsgiBundleApplicationContext;
/**
* Application context backed by an OSGi bundle.
*
* This application context will publish itself as a service using the service
* name "<bundle-symbolic-name>-springApplicationContext". To specify an
* alternate service name, use the org.springframework.context.service.name
* manifest header in the bundle manifest. For example:
*
* <code>
* org.springframework.context.service.name=myApplicationContextService
* </code>
*
* TODO: provide means to access OSGi services etc. through this application
* context?
*
* TODO: think about whether restricting config files to bundle: is the right
* thing to do
*
* TODO: listen to parent application context service, automatically rebind and
* refresh if it goes away and comes back (Costin: the logic can be placed
* inside the lookupParentAppContext proxy).
*
* TODO: add default locations (take intoa ccount also namespaces)
*
* @author Adrian Colyer
* @author Costin Leau
* @since 2.0
*/
public class OsgiBundleXmlApplicationContext extends AbstractRefreshableOsgiBundleApplicationContext {
/**
* Default config location for the root context (given as header inside the
* OSGi bundle)
*/
public static final String APPLICATION_CONTEXT_SERVICE_NAME_HEADER = "org.springframework.context.service.name";
/** Default application context suffix */
private static final String APPLICATION_CONTEXT_SERVICE_POSTFIX = "-springApplicationContext";
/** OSGi namespace handlers * */
private static final String OSGI_SPRING_HANDLERS_LOCATION = "org/springframework/osgi/handlers/spring.handlers";
/** Used for publishing the app context * */
private ServiceRegistration serviceRegistration;
public OsgiBundleXmlApplicationContext(BundleContext aBundleContext, String[] configLocations) {
this(null, aBundleContext, configLocations);
}
public OsgiBundleXmlApplicationContext(ApplicationContext parent, BundleContext aBundleContext,
String[] configLocations) {
super(parent);
setConfigLocations(configLocations);
setBundleContext(aBundleContext);
refresh();
publishContextAsOsgiService();
}
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
*
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
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 this context's
// resource loading environment.
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
/**
* We can't look in META-INF across bundles when using osgi, so we need to
* change the default namespace handler (spring.handlers) location with a
* custom resolver.
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
beanDefinitionReader.setNamespaceHandlerResolver(new DefaultNamespaceHandlerResolver(getClassLoader(),
OSGI_SPRING_HANDLERS_LOCATION));
}
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
* <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 = getConfigLocations();
if (configLocations != null) {
for (int i = 0; i < configLocations.length; i++) {
reader.loadBeanDefinitions(configLocations[i]);
}
}
}
protected void publishContextAsOsgiService() {
Dictionary serviceProperties = new Properties();
serviceProperties.put(APPLICATION_CONTEXT_SERVICE_NAME_HEADER, getServiceName());
this.serviceRegistration = getBundleContext().registerService(
new String[] { ApplicationContext.class.getName() }, this, serviceProperties);
}
/**
* The name we will use when publishing this application context as an OSGi
* service. If the APPLICATION_CONTEXT_SERVICE_NAME_HEADER manifest header
* is present, we use the user given name, otherwise we derive a name from
* the bundle symbolic name.
*/
protected String getServiceName() {
String userSpecifiedName = (String) getBundle().getHeaders().get(APPLICATION_CONTEXT_SERVICE_NAME_HEADER);
if (userSpecifiedName != null) {
return userSpecifiedName;
}
else {
return getBundle().getSymbolicName() + APPLICATION_CONTEXT_SERVICE_POSTFIX;
}
}
public void close() {
if (this.serviceRegistration != null) {
this.serviceRegistration.unregister();
}
super.close();
}
}