1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
204 super.doClose();
205 }
206
207
208
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
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
254 registerPropertyEditors(beanFactory);
255
256
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
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
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
319 Class[] classes = org.springframework.osgi.util.internal.ClassUtils.getClassHierarchy(getClass(),
320 org.springframework.osgi.util.internal.ClassUtils.INCLUDE_INTERFACES);
321
322
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
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
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 }