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  
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=&lt;bundle symbolic name&gt;</li>
73   * <li>BundleVersion=&lt;bundle version&gt;</li>
74   * <li>org.springframework.osgi.bean.name="&lt;bean name&gt;</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 		// used if the published bean is itself a ServiceFactory
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 			// prefer returning a target first (for example to avoid singleton
113 			// lookups)
114 			Object bean = (target != null ? target : beanFactory.getBean(targetBeanName));
115 
116 			// if we get a ServiceFactory, call its method
117 			if (bean instanceof ServiceFactory) {
118 				serviceFactory = (ServiceFactory) bean;
119 				bean = serviceFactory.getService(bundle, serviceRegistration);
120 			}
121 
122 			// add TCCL behaviour only if needed
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 		// if we have a name, we need a bean factory
226 		if (hasNamedBean) {
227 			Assert.notNull(beanFactory, "Required property 'beanFactory' has not been set.");
228 		}
229 
230 		// initialize bean only when dealing with singletons and named beans
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 			// when running inside a container, add the dependency between this bean and the target one
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 		// sanity check
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 		// check visibility type
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 				// no need to validate targetBeanName (already did)
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 		// add service properties
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 		// if we have a nested bean / non-Spring managed object
348 		String beanName = (!hasNamedBean ? ObjectUtils.getIdentityHexString(target) : targetBeanName);
349 
350 		Dictionary serviceProperties = mergeServiceProperties(beanName);
351 
352 		Class[] intfs = interfaces;
353 
354 		// filter classes based on visibility
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 		// filter duplicates
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 		// create an array of classnames (used for registering the service)
389 		String[] names = ClassUtils.toStringArray(classes);
390 
391 		// sort the names in alphabetical order (eases debugging)
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 		// if we do have a bundle scope, use ServiceFactory decoration
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 				// if for some reason, the passed in BeanFactory can't be
415 				// queried for scopes and we do
416 				// have a bean reference, apply scoped decoration.
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 }