1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.springframework.osgi.service.exporter.support;
19
20 import java.util.Arrays;
21 import java.util.Dictionary;
22 import java.util.LinkedHashSet;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.aopalliance.aop.Advice;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.osgi.framework.Bundle;
30 import org.osgi.framework.BundleContext;
31 import org.osgi.framework.Constants;
32 import org.osgi.framework.ServiceFactory;
33 import org.osgi.framework.ServiceRegistration;
34 import org.springframework.aop.framework.ProxyFactory;
35 import org.springframework.beans.BeansException;
36 import org.springframework.beans.factory.BeanClassLoaderAware;
37 import org.springframework.beans.factory.BeanFactory;
38 import org.springframework.beans.factory.BeanFactoryAware;
39 import org.springframework.beans.factory.BeanNameAware;
40 import org.springframework.beans.factory.FactoryBean;
41 import org.springframework.beans.factory.InitializingBean;
42 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
43 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
44 import org.springframework.core.Ordered;
45 import org.springframework.osgi.context.BundleContextAware;
46 import org.springframework.osgi.context.internal.classloader.ChainedClassLoader;
47 import org.springframework.osgi.context.support.internal.OsgiBundleScope;
48 import org.springframework.osgi.service.exporter.OsgiServicePropertiesResolver;
49 import org.springframework.osgi.service.exporter.support.internal.controller.ExporterController;
50 import org.springframework.osgi.service.exporter.support.internal.controller.ExporterInternalActions;
51 import org.springframework.osgi.service.util.internal.aop.ProxyUtils;
52 import org.springframework.osgi.service.util.internal.aop.ServiceTCCLInterceptor;
53 import org.springframework.osgi.util.DebugUtils;
54 import org.springframework.osgi.util.OsgiServiceUtils;
55 import org.springframework.osgi.util.internal.ClassUtils;
56 import org.springframework.osgi.util.internal.MapBasedDictionary;
57 import org.springframework.util.Assert;
58 import org.springframework.util.CollectionUtils;
59 import org.springframework.util.ObjectUtils;
60 import org.springframework.util.StringUtils;
61
62 /**
63 * FactoryBean that transparently publishes other beans in the same application
64 * context as OSGi services returning the ServiceRegistration for the given
65 * object. Also known as an <em>exporter</em> this class handle the
66 * registration and unregistration of an OSGi service for the backing/target
67 * object.
68 *
69 * <p/> The service properties used when publishing the service are determined
70 * by the OsgiServicePropertiesResolver. The default implementation uses
71 * <ul>
72 * <li>BundleSymbolicName=<bundle symbolic name></li>
73 * <li>BundleVersion=<bundle version></li>
74 * <li>org.springframework.osgi.bean.name="<bean name></li>
75 * </ul>
76 *
77 * <p/> <strong>Note:</strong>If thread context class loader management is used ({@link #setContextClassLoader(ExportContextClassLoader)},
78 * since proxying is required, the target class has to meet certain criterion
79 * described in the Spring AOP documentation. In short, final classes are not
80 * supported when class enhancement is used.
81 *
82 * @author Adrian Colyer
83 * @author Costin Leau
84 * @author Hal Hildebrand
85 * @author Andy Piper
86 *
87 */
88 public class OsgiServiceFactoryBean extends AbstractOsgiServiceExporter implements BeanClassLoaderAware,
89 BeanFactoryAware, BeanNameAware, BundleContextAware, FactoryBean, InitializingBean, Ordered {
90
91 /**
92 * ServiceFactory used for publishing the service beans. Acts as a a wrapper
93 * around special beans (such as ServiceFactory) and delegates to the
94 * container each time a bundle requests the service for the first time.
95 *
96 * @author Costin Leau
97 */
98 private class PublishingServiceFactory implements ServiceFactory {
99
100
101 private ServiceFactory serviceFactory;
102
103 private Class[] classes;
104
105
106 protected PublishingServiceFactory(Class[] classes) {
107 this.classes = classes;
108 }
109
110 public Object getService(Bundle bundle, ServiceRegistration serviceRegistration) {
111
112
113
114 Object bean = (target != null ? target : beanFactory.getBean(targetBeanName));
115
116
117 if (bean instanceof ServiceFactory) {
118 serviceFactory = (ServiceFactory) bean;
119 bean = serviceFactory.getService(bundle, serviceRegistration);
120 }
121
122
123 if (contextClassLoader == ExportContextClassLoader.SERVICE_PROVIDER) {
124 Object proxy = wrapWithClassLoaderManagingProxy(bean, classes);
125 return proxy;
126 }
127 else {
128 return bean;
129 }
130 }
131
132 public void ungetService(Bundle bundle, ServiceRegistration serviceRegistration, Object bean) {
133 if (serviceFactory != null)
134 serviceFactory.ungetService(bundle, serviceRegistration, bean);
135 }
136 }
137
138 /**
139 * Wrapper around internal commands.
140 *
141 * @author Costin Leau
142 *
143 */
144 private class Executor implements ExporterInternalActions {
145
146 public void registerService() {
147 OsgiServiceFactoryBean.this.registerService();
148 }
149
150 public void registerServiceAtStartup(boolean register) {
151 synchronized (lock) {
152 registerAtStartup = register;
153 }
154 }
155
156 public void unregisterService() {
157 OsgiServiceFactoryBean.this.unregisterService();
158 }
159 };
160
161
162 private static final Log log = LogFactory.getLog(OsgiServiceFactoryBean.class);
163
164 private BundleContext bundleContext;
165
166 private OsgiServicePropertiesResolver propertiesResolver;
167
168 private BeanFactory beanFactory;
169
170 private ServiceRegistration serviceRegistration;
171
172 private Map serviceProperties;
173
174 private int ranking;
175
176 private String targetBeanName;
177
178 private boolean hasNamedBean;
179
180 private Class[] interfaces;
181
182 private AutoExport autoExport = AutoExport.DISABLED;
183
184 private ExportContextClassLoader contextClassLoader = ExportContextClassLoader.UNMANAGED;
185
186 private Object target;
187
188 private Class targetClass;
189
190 /** Default value is same as non-ordered */
191 private int order = Ordered.LOWEST_PRECEDENCE;
192
193 private ClassLoader classLoader;
194
195 /** class loader used by the aop infrastructure */
196 private ClassLoader aopClassLoader;
197
198 /** exporter bean name */
199 private String beanName;
200
201 /** registration sanity flag */
202 private boolean serviceRegistered = false;
203
204 /** register at startup by default */
205 private boolean registerAtStartup = true;
206
207 /** synchronization lock */
208 private final Object lock = new Object();
209
210 /** internal behaviour controller */
211 private final ExporterController controller;
212
213
214 public OsgiServiceFactoryBean() {
215 controller = new ExporterController(new Executor());
216 }
217
218 public void afterPropertiesSet() throws Exception {
219 Assert.notNull(bundleContext, "required property 'bundleContext' has not been set");
220
221 hasNamedBean = StringUtils.hasText(targetBeanName);
222
223 Assert.isTrue(hasNamedBean || target != null, "Either 'targetBeanName' or 'target' properties have to be set.");
224
225
226 if (hasNamedBean) {
227 Assert.notNull(beanFactory, "Required property 'beanFactory' has not been set.");
228 }
229
230
231 if (hasNamedBean) {
232 Assert.isTrue(beanFactory.containsBean(targetBeanName), "Cannot locate bean named '" + targetBeanName
233 + "' inside the running bean factory.");
234
235 if (beanFactory.isSingleton(targetBeanName)) {
236 target = beanFactory.getBean(targetBeanName);
237 targetClass = target.getClass();
238 }
239 else {
240 targetClass = beanFactory.getType(targetBeanName);
241 }
242
243
244 addBeanFactoryDependency();
245 }
246 else
247 targetClass = target.getClass();
248
249 if (propertiesResolver == null) {
250 propertiesResolver = new BeanNameServicePropertiesResolver();
251 ((BeanNameServicePropertiesResolver) propertiesResolver).setBundleContext(bundleContext);
252 }
253
254
255 if (interfaces == null) {
256 if (AutoExport.DISABLED.equals(autoExport))
257 throw new IllegalArgumentException(
258 "No service interface(s) specified and auto-export discovery disabled; change at least one of these properties.");
259 interfaces = new Class[0];
260 }
261
262 else {
263 for (int interfaceIndex = 0; interfaceIndex < interfaces.length; interfaceIndex++) {
264 Class intf = interfaces[interfaceIndex];
265 Assert.isAssignable(intf, targetClass, "Exported service object does not implement the given interface: ");
266 }
267 }
268
269 boolean shouldRegisterAtStartup;
270 synchronized (lock) {
271 shouldRegisterAtStartup = registerAtStartup;
272 }
273
274 if (shouldRegisterAtStartup)
275 registerService();
276 }
277
278 private void addBeanFactoryDependency() {
279 if (beanFactory instanceof ConfigurableBeanFactory) {
280 ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
281 if (StringUtils.hasText(beanName) && cbf.containsBean(beanName)) {
282
283 cbf.registerDependentBean(targetBeanName, BeanFactory.FACTORY_BEAN_PREFIX + beanName);
284 cbf.registerDependentBean(targetBeanName, beanName);
285 }
286 }
287 else {
288 log.warn("The running bean factory cannot support dependencies between beans - importer/exporter dependency cannot be enforced");
289 }
290 }
291
292 /**
293 * Proxy the target object with an interceptor that manages the context
294 * classloader. This should be applied only if such management is needed.
295 *
296 * @param target
297 * @return
298 */
299 private Object wrapWithClassLoaderManagingProxy(final Object target, Class[] interfaces) {
300 try {
301
302 return ProxyUtils.createProxy(interfaces, target, aopClassLoader, bundleContext,
303 new Advice[] { new ServiceTCCLInterceptor(classLoader) });
304 }
305 catch (Throwable th) {
306 log.error("Cannot create TCCL managed proxy; falling back to the naked object", th);
307 if (th instanceof NoClassDefFoundError) {
308 NoClassDefFoundError ncdfe = (NoClassDefFoundError) th;
309 if (log.isWarnEnabled()) {
310 DebugUtils.debugClassLoadingThrowable(ncdfe, bundleContext.getBundle(), this.interfaces);
311 }
312 throw ncdfe;
313 }
314 }
315 return target;
316 }
317
318 private Dictionary mergeServiceProperties(String beanName) {
319 MapBasedDictionary props = new MapBasedDictionary(propertiesResolver.getServiceProperties(beanName));
320
321 props.putAll((Map) props);
322
323
324 if (serviceProperties != null)
325 props.putAll(serviceProperties);
326
327 if (ranking != 0) {
328 props.put(org.osgi.framework.Constants.SERVICE_RANKING, new Integer(ranking));
329 }
330 return props;
331 }
332
333 /**
334 * Publishes the given object as an OSGi service. It simply assembles the
335 * classes required for publishing and then delegates the actual
336 * registration to a dedicated method.
337 */
338 void registerService() {
339
340 synchronized (lock) {
341 if (serviceRegistered)
342 return;
343 else
344 serviceRegistered = true;
345 }
346
347
348 String beanName = (!hasNamedBean ? ObjectUtils.getIdentityHexString(target) : targetBeanName);
349
350 Dictionary serviceProperties = mergeServiceProperties(beanName);
351
352 Class[] intfs = interfaces;
353
354
355 ClassLoader beanClassLoader = ClassUtils.getClassLoader(targetClass);
356 Class[] autoDetectedClasses = ClassUtils.getVisibleClasses(autoExport.getExportedClasses(targetClass),
357 beanClassLoader);
358
359 if (log.isTraceEnabled())
360 log.trace("Autoexport mode [" + autoExport + "] discovered on class [" + targetClass + "] classes "
361 + ObjectUtils.nullSafeToString(autoDetectedClasses));
362
363
364 Set classes = new LinkedHashSet(intfs.length + autoDetectedClasses.length);
365
366 CollectionUtils.mergeArrayIntoCollection(intfs, classes);
367 CollectionUtils.mergeArrayIntoCollection(autoDetectedClasses, classes);
368
369 Class[] mergedClasses = (Class[]) classes.toArray(new Class[classes.size()]);
370
371 ServiceRegistration reg = registerService(mergedClasses, serviceProperties);
372
373 serviceRegistration = notifyListeners(target, (Map) serviceProperties, reg);
374 }
375
376 /**
377 * Registration method.
378 *
379 * @param classes
380 * @param serviceProperties
381 * @return the ServiceRegistration
382 */
383 ServiceRegistration registerService(Class[] classes, Dictionary serviceProperties) {
384 Assert.notEmpty(
385 classes,
386 "at least one class has to be specified for exporting (if autoExport is enabled then maybe the object doesn't implement any interface)");
387
388
389 String[] names = ClassUtils.toStringArray(classes);
390
391
392 Arrays.sort(names);
393
394 log.info("Publishing service under classes [" + ObjectUtils.nullSafeToString(names) + "]");
395
396 ServiceFactory serviceFactory = new PublishingServiceFactory(classes);
397
398 if (isBeanBundleScoped())
399 serviceFactory = new OsgiBundleScope.BundleScopeServiceFactory(serviceFactory);
400
401 return bundleContext.registerService(names, serviceFactory, serviceProperties);
402 }
403
404 private boolean isBeanBundleScoped() {
405 boolean bundleScoped = false;
406
407 if (targetBeanName != null) {
408 if (beanFactory instanceof ConfigurableListableBeanFactory) {
409 String beanScope = ((ConfigurableListableBeanFactory) beanFactory).getMergedBeanDefinition(
410 targetBeanName).getScope();
411 bundleScoped = OsgiBundleScope.SCOPE_NAME.equals(beanScope);
412 }
413 else
414
415
416
417 bundleScoped = true;
418 }
419 return bundleScoped;
420 }
421
422 public void setBeanClassLoader(ClassLoader classLoader) {
423 this.classLoader = classLoader;
424 this.aopClassLoader = new ChainedClassLoader(new ClassLoader[] { classLoader,
425 ProxyFactory.class.getClassLoader() });
426 }
427
428 /**
429 * {@inheritDoc}
430 *
431 * <p/> Returns a {@link ServiceRegistration} to the OSGi service for the
432 * target object.
433 */
434 public Object getObject() throws Exception {
435 return serviceRegistration;
436 }
437
438 public Class getObjectType() {
439 return (serviceRegistration != null ? serviceRegistration.getClass() : ServiceRegistration.class);
440 }
441
442 public boolean isSingleton() {
443 return false;
444 }
445
446 void unregisterService() {
447 synchronized (lock) {
448 if (!serviceRegistered)
449 return;
450 else
451 serviceRegistered = false;
452 }
453
454 unregisterService(serviceRegistration);
455 serviceRegistration = null;
456 }
457
458 /**
459 * Unregisters (literally stops) a service.
460 *
461 * @param registration
462 */
463 void unregisterService(ServiceRegistration registration) {
464 if (OsgiServiceUtils.unregisterService(registration)) {
465 log.info("Unregistered service [" + registration + "]");
466 }
467 }
468
469 /**
470 * Sets the context class loader management strategy to use when invoking
471 * operations on the exposed target bean. By default,
472 * {@link ExportContextClassLoader#UNMANAGED} is used.
473 *
474 * <p/> <strong>Note:</strong> Since proxying is required for context class
475 * loader manager, the target class has to meet certain criterias described
476 * in the Spring AOP documentation. In short, final classes are not
477 * supported when class enhancement is used.
478 *
479 * @param ccl context class loader strategy to use
480 * @see ExportContextClassLoader
481 */
482 public void setContextClassLoader(ExportContextClassLoader ccl) {
483 Assert.notNull(ccl);
484 this.contextClassLoader = ccl;
485 }
486
487 /**
488 * Returns the object exported as an OSGi service.
489 *
490 * @return the object exported as an OSGi service
491 */
492 public Object getTarget() {
493 return target;
494 }
495
496 /**
497 * Sets the given object to be export as an OSGi service. Normally used when
498 * the exported service is a nested bean or an object not managed by the
499 * Spring container. Note that the passed target instance is ignored if
500 * {@link #setTargetBeanName(String)} is used.
501 *
502 * @param target the object to be exported as an OSGi service
503 */
504 public void setTarget(Object target) {
505 this.target = target;
506 }
507
508 /**
509 * Returns the target bean name.
510 *
511 * @return the target object bean name
512 */
513 public String getTargetBeanName() {
514 return targetBeanName;
515 }
516
517 /**
518 * Sets the name of the bean managed by the Spring container, which will be
519 * exported as an OSGi service. This method is normally what most use-cases
520 * need, rather then {@link #setTarget(Object)}.
521 *
522 * @param name target bean name
523 */
524 public void setTargetBeanName(String name) {
525 this.targetBeanName = name;
526 }
527
528 /**
529 * Sets the strategy used for automatically publishing classes. This allows
530 * the exporter to use the target class hierarchy and/or interfaces for
531 * registering the OSGi service. By default, autoExport is disabled
532 * {@link AutoExport#DISABLED}.
533 *
534 * @param classExporter class exporter used for automatically publishing
535 * service classes.
536 *
537 * @see AutoExport
538 *
539 */
540 public void setAutoExport(AutoExport classExporter) {
541 Assert.notNull(classExporter);
542 this.autoExport = classExporter;
543 }
544
545 /**
546 * Returns the properties used when exporting the target as an OSGi service.
547 *
548 * @return properties used for exporting the target
549 */
550 public Map getServiceProperties() {
551 return serviceProperties;
552 }
553
554 /**
555 * Sets the properties used when exposing the target as an OSGi service.
556 *
557 * @param serviceProperties properties used for exporting the target as an
558 * OSGi service
559 */
560 public void setServiceProperties(Map serviceProperties) {
561 this.serviceProperties = serviceProperties;
562 }
563
564 /**
565 * Returns the OSGi ranking used when publishing the service.
566 *
567 * @return service ranking used when publishing the service
568 */
569 public int getRanking() {
570 return ranking;
571 }
572
573 /**
574 * Shortcut for setting the ranking property of the published service.
575 *
576 *
577 * @param ranking service ranking
578 * @see Constants#SERVICE_RANKING
579 */
580 public void setRanking(int ranking) {
581 this.ranking = ranking;
582 }
583
584 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
585 this.beanFactory = beanFactory;
586 }
587
588 public void setBundleContext(BundleContext context) {
589 this.bundleContext = context;
590 }
591
592 /**
593 * Returns the property resolver used for publishing the service.
594 *
595 * @return service property resolver
596 */
597 public OsgiServicePropertiesResolver getResolver() {
598 return this.propertiesResolver;
599 }
600
601 /**
602 * Sets the property resolver used when publishing the bean as an OSGi
603 * service.
604 *
605 * @param resolver service property resolver
606 */
607 public void setResolver(OsgiServicePropertiesResolver resolver) {
608 this.propertiesResolver = resolver;
609 }
610
611 /**
612 * Returns the interfaces that will be considered when exporting the target
613 * as an OSGi service.
614 *
615 * @return interfaces under which the target will be published as an OSGi
616 * service
617 */
618 public Class[] getInterfaces() {
619 return interfaces;
620 }
621
622 /**
623 * Sets the interfaces advertised by the service.These will be advertised in
624 * the OSGi space and are considered when looking for a service.
625 *
626 * @param interfaces array of classes to advertise
627 */
628 public void setInterfaces(Class[] interfaces) {
629 this.interfaces = interfaces;
630 }
631
632 public int getOrder() {
633 return order;
634 }
635
636 /**
637 * Set the ordering which will apply to this class's implementation of
638 * Ordered, used when applying multiple BeanPostProcessors.
639 * <p>
640 * Default value is <code>Integer.MAX_VALUE</code>, meaning that it's
641 * non-ordered.
642 *
643 * @param order ordering value
644 */
645 public void setOrder(int order) {
646 this.order = order;
647 }
648
649 /**
650 * Returns the bean name of this class when configured inside a Spring
651 * container.
652 *
653 * @return the bean name for this class
654 */
655 public String getBeanName() {
656 return beanName;
657 }
658
659 public void setBeanName(String name) {
660 this.beanName = name;
661 }
662 }