View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.osgi.context.support;
18  
19  import java.beans.PropertyEditor;
20  import java.io.IOException;
21  import java.util.Dictionary;
22  import java.util.Map;
23  
24  import org.osgi.framework.Bundle;
25  import org.osgi.framework.BundleContext;
26  import org.osgi.framework.Constants;
27  import org.osgi.framework.ServiceRegistration;
28  import org.springframework.beans.BeanUtils;
29  import org.springframework.beans.BeansException;
30  import org.springframework.beans.factory.BeanFactoryAware;
31  import org.springframework.beans.factory.config.BeanPostProcessor;
32  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
33  import org.springframework.beans.factory.config.Scope;
34  import org.springframework.context.ApplicationContext;
35  import org.springframework.context.ApplicationContextException;
36  import org.springframework.context.support.AbstractRefreshableApplicationContext;
37  import org.springframework.core.io.Resource;
38  import org.springframework.core.io.support.ResourcePatternResolver;
39  import org.springframework.osgi.context.BundleContextAware;
40  import org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext;
41  import org.springframework.osgi.context.internal.classloader.AopClassLoaderFactory;
42  import org.springframework.osgi.context.support.internal.OsgiBundleScope;
43  import org.springframework.osgi.io.OsgiBundleResource;
44  import org.springframework.osgi.io.OsgiBundleResourcePatternResolver;
45  import org.springframework.osgi.util.OsgiBundleUtils;
46  import org.springframework.osgi.util.OsgiServiceUtils;
47  import org.springframework.osgi.util.OsgiStringUtils;
48  import org.springframework.osgi.util.internal.MapBasedDictionary;
49  import org.springframework.util.Assert;
50  import org.springframework.util.ClassUtils;
51  import org.springframework.util.ObjectUtils;
52  import org.springframework.util.StringUtils;
53  
54  /**
55   * 
56   * <code>AbstractRefreshableApplicationContext</code> subclass that implements
57   * the {@link ConfigurableOsgiBundleApplicationContext} interface for OSGi
58   * environments. Pre-implements a <code>configLocation</code> property, to be
59   * populated through the <code>ConfigurableOsgiApplicationContext</code>
60   * interface after OSGi bundle startup.
61   * 
62   * <p>
63   * This class is as easy to subclass as
64   * <code>AbstractRefreshableApplicationContext</code>(see the javadoc for
65   * details): all you need to implement is the <code>loadBeanDefinitions</code>
66   * method Note that implementations are supposed to load bean definitions from
67   * the files specified by the locations returned by
68   * <code>getConfigLocations</code> method.
69   * 
70   * <p>
71   * In addition to the special beans detected by
72   * <code>AbstractApplicationContext</code>, this class registers the
73   * <code>BundleContextAwareProcessor</code> for processing beans that
74   * implement the <code>BundleContextAware</code> interface. Also it interprets
75   * resource paths as OSGi bundle resources (either from the bundle class space,
76   * bundle space or jar space).
77   * 
78   * <p>
79   * This application context implementation offers the OSGi-specific,
80   * <em>bundle</em> scope.
81   * 
82   * <p>
83   * <strong>Note:</strong> <code>OsgiApplicationContext</code> implementations
84   * are generally supposed to configure themselves based on the configuration
85   * received through the <code>ConfigurableOsgiBundleApplicationContext</code>
86   * interface. In contrast, a stand-alone application context might allow for
87   * configuration in custom startup code (for example,
88   * <code>GenericApplicationContext</code>).
89   * 
90   * @author Costin Leau
91   * @author Adrian Colyer
92   * @author Hal Hildebrand
93   * 
94   */
95  public abstract class AbstractOsgiBundleApplicationContext extends AbstractRefreshableApplicationContext implements
96  		ConfigurableOsgiBundleApplicationContext {
97  
98  	private static final String EXPORTER_IMPORTER_DEPENDENCY_MANAGER = "org.springframework.osgi.service.dependency.internal.MandatoryDependencyBeanPostProcessor";
99  
100 	/** OSGi bundle - determined from the BundleContext */
101 	private Bundle bundle;
102 
103 	/** OSGi bundle context */
104 	private BundleContext bundleContext;
105 
106 	/** Path to configuration files */
107 	private String[] configLocations;
108 
109 	/** Used for publishing the app context */
110 	private ServiceRegistration serviceRegistration;
111 
112 	/** Should context be published as an OSGi service? */
113 	private boolean publishContextAsService = true;
114 
115 	/** class loader used for loading the beans */
116 	private ClassLoader classLoader;
117 
118 	/**
119 	 * Internal pattern resolver. The parent one can't be used since it is being
120 	 * instantiated inside the constructor when the bundle field is not
121 	 * initialized yet.
122 	 */
123 	private ResourcePatternResolver osgiPatternResolver;
124 
125 
126 	/**
127 	 * Creates a new <code>AbstractOsgiBundleApplicationContext</code> with no
128 	 * parent.
129 	 */
130 	public AbstractOsgiBundleApplicationContext() {
131 		super();
132 		setDisplayName("Root OsgiBundleApplicationContext");
133 	}
134 
135 	/**
136 	 * Creates a new <code>AbstractOsgiBundleApplicationContext</code> with
137 	 * the given parent context.
138 	 * 
139 	 * @param parent the parent context
140 	 */
141 	public AbstractOsgiBundleApplicationContext(ApplicationContext parent) {
142 		super(parent);
143 	}
144 
145 	/**
146 	 * {@inheritDoc}
147 	 * 
148 	 * <p/> Will automatically determine the bundle, create a new
149 	 * <code>ResourceLoader</code> (and set its <code>ClassLoader</code> (if
150 	 * none is set already) to a custom implementation that will delegate the
151 	 * calls to the bundle).
152 	 */
153 	public void setBundleContext(BundleContext bundleContext) {
154 		this.bundleContext = bundleContext;
155 		this.bundle = bundleContext.getBundle();
156 		this.osgiPatternResolver = createResourcePatternResolver();
157 
158 		if (getClassLoader() == null)
159 			this.setClassLoader(createBundleClassLoader(this.bundle));
160 
161 		this.setDisplayName(ClassUtils.getShortName(getClass()) + "(bundle=" + getBundleSymbolicName() + ", config="
162 				+ StringUtils.arrayToCommaDelimitedString(getConfigLocations()) + ")");
163 	}
164 
165 	public BundleContext getBundleContext() {
166 		return this.bundleContext;
167 	}
168 
169 	public Bundle getBundle() {
170 		return this.bundle;
171 	}
172 
173 	public void setConfigLocations(String[] configLocations) {
174 		this.configLocations = configLocations;
175 	}
176 
177 	/**
178 	 * Returns this application context configuration locations. The default
179 	 * implementation will check whether there are any locations configured and,
180 	 * if not, will return the default locations.
181 	 * 
182 	 * @return application context configuration locations.
183 	 * @see #getDefaultConfigLocations()
184 	 */
185 	public String[] getConfigLocations() {
186 		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
187 	}
188 
189 	/**
190 	 * Unregister the ApplicationContext OSGi service (in case there is any).
191 	 */
192 	protected void doClose() {
193 		if (!OsgiServiceUtils.unregisterService(serviceRegistration)) {
194 			logger.info("Unpublishing application context OSGi service for bundle "
195 					+ OsgiStringUtils.nullSafeNameAndSymName(bundle));
196 			serviceRegistration = null;
197 		}
198 		else {
199 			if (publishContextAsService)
200 				logger.info("Application Context service already unpublished");
201 		}
202 
203 		// call super class
204 		super.doClose();
205 	}
206 
207 	/*
208 	 * Clean up any beans from the bundle scope.
209 	 */
210 	protected void destroyBeans() {
211 		super.destroyBeans();
212 
213 		try {
214 			cleanOsgiBundleScope(getBeanFactory());
215 		}
216 		catch (Exception ex) {
217 			logger.warn("got exception when closing", ex);
218 		}
219 	}
220 
221 	/**
222 	 * Returns the default configuration locations to use, for the case where no
223 	 * explicit configuration locations have been specified.
224 	 * <p>
225 	 * Default implementation returns <code>null</code>, requiring explicit
226 	 * configuration locations.
227 	 * 
228 	 * @return application context default configuration locations
229 	 * @see #setConfigLocations
230 	 */
231 	protected String[] getDefaultConfigLocations() {
232 		return null;
233 	}
234 
235 	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
236 		super.postProcessBeanFactory(beanFactory);
237 
238 		beanFactory.addBeanPostProcessor(new BundleContextAwareProcessor(this.bundleContext));
239 		beanFactory.ignoreDependencyInterface(BundleContextAware.class);
240 
241 		enforceExporterImporterDependency(beanFactory);
242 
243 		// add bundleContext bean
244 		if (!beanFactory.containsLocalBean(BUNDLE_CONTEXT_BEAN_NAME)) {
245 			logger.debug("Registering BundleContext as a bean named " + BUNDLE_CONTEXT_BEAN_NAME);
246 			beanFactory.registerSingleton(BUNDLE_CONTEXT_BEAN_NAME, this.bundleContext);
247 		}
248 		else {
249 			logger.warn("A bean named " + BUNDLE_CONTEXT_BEAN_NAME
250 					+ " already exists; the bundleContext will not be registered as a bean");
251 		}
252 
253 		// register property editors
254 		registerPropertyEditors(beanFactory);
255 
256 		// register a 'bundle' scope
257 		beanFactory.registerScope(OsgiBundleScope.SCOPE_NAME, new OsgiBundleScope());
258 	}
259 
260 	/**
261 	 * Takes care of enforcing the relationship between exporter and importers.
262 	 * 
263 	 * @param beanFactory
264 	 */
265 	private void enforceExporterImporterDependency(ConfigurableListableBeanFactory beanFactory) {
266 		// create the service manager
267 		ClassLoader loader = AbstractOsgiBundleApplicationContext.class.getClassLoader();
268 		Object instance = null;
269 		try {
270 			Class managerClass = loader.loadClass(EXPORTER_IMPORTER_DEPENDENCY_MANAGER);
271 			instance = BeanUtils.instantiateClass(managerClass);
272 		}
273 		catch (ClassNotFoundException cnfe) {
274 			throw new ApplicationContextException("Cannot load class " + EXPORTER_IMPORTER_DEPENDENCY_MANAGER, cnfe);
275 		}
276 
277 		// sanity check
278 		Assert.isInstanceOf(BeanFactoryAware.class, instance);
279 		Assert.isInstanceOf(BeanPostProcessor.class, instance);
280 		((BeanFactoryAware) instance).setBeanFactory(beanFactory);
281 		beanFactory.addBeanPostProcessor((BeanPostProcessor) instance);
282 	}
283 
284 	/**
285 	 * Register OSGi-specific {@link PropertyEditor}s.
286 	 * 
287 	 * @param beanFactory beanFactory used for registration.
288 	 */
289 	private void registerPropertyEditors(ConfigurableListableBeanFactory beanFactory) {
290 		beanFactory.addPropertyEditorRegistrar(new OsgiPropertyEditorRegistrar());
291 	}
292 
293 	private void cleanOsgiBundleScope(ConfigurableListableBeanFactory beanFactory) {
294 		Scope scope = beanFactory.getRegisteredScope(OsgiBundleScope.SCOPE_NAME);
295 		if (scope != null && scope instanceof OsgiBundleScope) {
296 			if (logger.isDebugEnabled())
297 				logger.debug("Destroying existing bundle scope beans...");
298 			((OsgiBundleScope) scope).destroy();
299 		}
300 	}
301 
302 	/**
303 	 * Publish the application context as an OSGi service. The method internally
304 	 * takes care of parsing the bundle headers and determined if actual
305 	 * publishing is required or not.
306 	 * 
307 	 */
308 	void publishContextAsOsgiServiceIfNecessary() {
309 		if (publishContextAsService) {
310 			Dictionary serviceProperties = new MapBasedDictionary();
311 
312 			customizeApplicationContextServiceProperties((Map) serviceProperties);
313 
314 			if (logger.isInfoEnabled()) {
315 				logger.info("Publishing application context as OSGi service with properties " + serviceProperties);
316 			}
317 
318 			// export only interfaces
319 			Class[] classes = org.springframework.osgi.util.internal.ClassUtils.getClassHierarchy(getClass(),
320 				org.springframework.osgi.util.internal.ClassUtils.INCLUDE_INTERFACES);
321 
322 			// filter classes based on visibility
323 			Class[] filterClasses = org.springframework.osgi.util.internal.ClassUtils.getVisibleClasses(classes,
324 				this.getClass().getClassLoader());
325 
326 			String[] serviceNames = org.springframework.osgi.util.internal.ClassUtils.toStringArray(filterClasses);
327 
328 			if (logger.isDebugEnabled())
329 				logger.debug("Publishing service under classes " + ObjectUtils.nullSafeToString(serviceNames));
330 
331 			// Publish under all the significant interfaces we see
332 			this.serviceRegistration = getBundleContext().registerService(serviceNames, this, serviceProperties);
333 		}
334 		else {
335 			if (logger.isInfoEnabled()) {
336 				logger.info("Not publishing application context OSGi service for bundle "
337 						+ OsgiStringUtils.nullSafeNameAndSymName(bundle));
338 			}
339 		}
340 	}
341 
342 	/**
343 	 * Customizes the properties of the application context OSGi service. This
344 	 * method is called only if the application context will be published as an
345 	 * OSGi service.
346 	 * 
347 	 * <p/>The default implementation stores the bundle symbolic name under
348 	 * {@link Constants#BUNDLE_SYMBOLICNAME} and
349 	 * {@link ConfigurableOsgiBundleApplicationContext#APPLICATION_CONTEXT_SERVICE_PROPERTY_NAME}
350 	 * and the bundle version under {@link Constants#BUNDLE_VERSION} property.
351 	 * 
352 	 * Can be overridden by subclasses to add more properties if needed (for
353 	 * example for web applications where multiple application contexts are
354 	 * available inside the same bundle).
355 	 * 
356 	 * @param serviceProperties service properties map (can be casted to
357 	 * {@link Dictionary})
358 	 */
359 	protected void customizeApplicationContextServiceProperties(Map serviceProperties) {
360 		serviceProperties.put(APPLICATION_CONTEXT_SERVICE_PROPERTY_NAME, getBundleSymbolicName());
361 		serviceProperties.put(Constants.BUNDLE_SYMBOLICNAME, getBundleSymbolicName());
362 		serviceProperties.put(Constants.BUNDLE_VERSION, OsgiBundleUtils.getBundleVersion(bundle));
363 	}
364 
365 	private String getBundleSymbolicName() {
366 		return OsgiStringUtils.nullSafeSymbolicName(getBundle());
367 	}
368 
369 	/**
370 	 * Creates an OSGi specific resource pattern resolver.
371 	 * 
372 	 * @return returns an OSGi specific pattern resolver.
373 	 */
374 	protected ResourcePatternResolver createResourcePatternResolver() {
375 		return new OsgiBundleResourcePatternResolver(getBundle());
376 	}
377 
378 	/**
379 	 * This implementation supports pattern matching inside the OSGi bundle.
380 	 * 
381 	 * @see OsgiBundleResourcePatternResolver
382 	 */
383 	protected ResourcePatternResolver getResourcePatternResolver() {
384 		return osgiPatternResolver;
385 	}
386 
387 	// delegate methods to a proper osgi resource loader
388 
389 	public ClassLoader getClassLoader() {
390 		return classLoader;
391 	}
392 
393 	public Resource getResource(String location) {
394 		return (osgiPatternResolver != null ? osgiPatternResolver.getResource(location) : null);
395 	}
396 
397 	public Resource[] getResources(String locationPattern) throws IOException {
398 		return (osgiPatternResolver != null ? osgiPatternResolver.getResources(locationPattern) : null);
399 	}
400 
401 	public void setClassLoader(ClassLoader classLoader) {
402 		this.classLoader = classLoader;
403 	}
404 
405 	protected Resource getResourceByPath(String path) {
406 		Assert.notNull(path, "Path is required");
407 		return new OsgiBundleResource(this.bundle, path);
408 	}
409 
410 	public void setPublishContextAsService(boolean publishContextAsService) {
411 		this.publishContextAsService = publishContextAsService;
412 	}
413 
414 	/**
415 	 * Create the class loader that delegates to the underlying OSGi bundle.
416 	 * 
417 	 * @param bundle
418 	 * @return
419 	 */
420 	private ClassLoader createBundleClassLoader(Bundle bundle) {
421 		return AopClassLoaderFactory.getBundleClassLoaderFor(bundle);
422 	}
423 }