| /****************************************************************************** |
| * Copyright (c) 2006, 2010 VMware Inc., Oracle Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * and Apache License v2.0 which accompanies this distribution. |
| * The Eclipse Public License is available at |
| * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 |
| * is available at http://www.opensource.org/licenses/apache2.0.php. |
| * You may elect to redistribute this code under either of these licenses. |
| * |
| * Contributors: |
| * VMware Inc. |
| * Oracle Inc. |
| *****************************************************************************/ |
| |
| package org.eclipse.gemini.blueprint.service.exporter.support; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.eclipse.gemini.blueprint.context.BundleContextAware; |
| import org.eclipse.gemini.blueprint.context.support.internal.classloader.ClassLoaderFactory; |
| import org.eclipse.gemini.blueprint.context.support.internal.scope.OsgiBundleScope; |
| import org.eclipse.gemini.blueprint.context.support.internal.security.SecurityUtils; |
| import org.eclipse.gemini.blueprint.service.exporter.OsgiServicePropertiesResolver; |
| import org.eclipse.gemini.blueprint.service.exporter.support.internal.controller.ExporterController; |
| import org.eclipse.gemini.blueprint.service.exporter.support.internal.controller.ExporterInternalActions; |
| import org.eclipse.gemini.blueprint.service.exporter.support.internal.support.*; |
| import org.eclipse.gemini.blueprint.util.OsgiServiceUtils; |
| import org.eclipse.gemini.blueprint.util.internal.ClassUtils; |
| import org.eclipse.gemini.blueprint.util.internal.MapBasedDictionary; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceRegistration; |
| import org.springframework.beans.BeansException; |
| import org.springframework.beans.factory.*; |
| import org.springframework.beans.factory.config.BeanDefinition; |
| import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
| import org.springframework.core.Ordered; |
| import org.springframework.util.Assert; |
| import org.springframework.util.CollectionUtils; |
| import org.springframework.util.ObjectUtils; |
| import org.springframework.util.StringUtils; |
| |
| import java.security.AccessControlContext; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| /** |
| * FactoryBean that transparently publishes other beans in the same application context as OSGi services returning the |
| * ServiceRegistration for the given object. Also known as an <em>exporter</em> this class handle the registration and |
| * unregistration of an OSGi service for the backing/target object. |
| * |
| * <p/> The service properties used when publishing the service are determined by the OsgiServicePropertiesResolver. The |
| * default implementation uses <ul> <li>BundleSymbolicName=<bundle symbolic name></li> |
| * <li>BundleVersion=<bundle version></li> <li>org.eclipse.gemini.blueprint.bean.name="<bean name></li> </ul> |
| * |
| * <p/> <strong>Note:</strong>If thread context class loader management is used ( |
| * {@link #setExportContextClassLoader(ExportContextClassLoaderEnum)}, since proxying is required, the target class has to meet |
| * certain criterion described in the Spring AOP documentation. In short, final classes are not supported when class |
| * enhancement is used. |
| * |
| * @author Adrian Colyer |
| * @author Costin Leau |
| * @author Hal Hildebrand |
| * @author Andy Piper |
| * |
| */ |
| public class OsgiServiceFactoryBean extends AbstractOsgiServiceExporter implements BeanClassLoaderAware, |
| BeanFactoryAware, BeanNameAware, BundleContextAware, FactoryBean<ServiceRegistration>, InitializingBean, |
| Ordered { |
| |
| /** |
| * Wrapper around internal commands. |
| * |
| * @author Costin Leau |
| * |
| */ |
| private class Executor implements ExporterInternalActions { |
| |
| public void registerService() { |
| OsgiServiceFactoryBean.this.registerService(); |
| } |
| |
| public void registerServiceAtStartup(boolean register) { |
| synchronized (lock) { |
| registerAtStartup = register; |
| } |
| } |
| |
| public void unregisterService() { |
| OsgiServiceFactoryBean.this.unregisterService(); |
| } |
| |
| public void callUnregisterOnStartup() { |
| OsgiServiceFactoryBean.this.resolver.startupUnregisterIfPossible(); |
| } |
| }; |
| |
| /** |
| * Callback that propagates the changes in the service properties to the registration object. |
| * |
| * @author Costin Leau |
| */ |
| private class PropertiesMonitor implements ServicePropertiesChangeListener { |
| |
| public void propertiesChange(ServicePropertiesChangeEvent event) { |
| serviceProperties = event.getServiceProperties(); |
| Dictionary dictionary = mergeServiceProperties(serviceProperties, beanName); |
| if (serviceRegistration != null) { |
| serviceRegistration.setProperties(dictionary); |
| } |
| } |
| } |
| |
| private static final Log log = LogFactory.getLog(OsgiServiceFactoryBean.class); |
| |
| private volatile BundleContext bundleContext; |
| private volatile OsgiServicePropertiesResolver propertiesResolver; |
| private volatile BeanFactory beanFactory; |
| private volatile ServiceRegistrationDecorator serviceRegistration; |
| private final ServiceRegistrationWrapper safeServiceRegistration = new ServiceRegistrationWrapper(null); |
| private volatile Map serviceProperties; |
| private volatile ServicePropertiesChangeListener propertiesListener; |
| private volatile int ranking; |
| private volatile String targetBeanName; |
| private boolean hasNamedBean; |
| private volatile Class<?>[] interfaces; |
| private InterfaceDetector interfaceDetector = DefaultInterfaceDetector.DISABLED; |
| private volatile ExportContextClassLoaderEnum contextClassLoader = ExportContextClassLoaderEnum.UNMANAGED; |
| private volatile Object target; |
| private volatile Class<?> targetClass; |
| /** Default value is same as non-ordered */ |
| private int order = Ordered.LOWEST_PRECEDENCE; |
| private ClassLoader classLoader; |
| /** class loader used by the aop infrastructure */ |
| private ClassLoader aopClassLoader; |
| /** exporter bean name */ |
| private String beanName; |
| /** registration sanity flag */ |
| private boolean serviceRegistered = false; |
| /** register at startup by default */ |
| private boolean registerAtStartup = true; |
| /** register the service by default */ |
| private boolean registerService = true; |
| /** synchronization lock */ |
| private final Object lock = new Object(); |
| /** internal behaviour controller */ |
| private final ExporterController controller; |
| private volatile LazyTargetResolver resolver; |
| private ListenerNotifier notifier; |
| private final AtomicBoolean activated = new AtomicBoolean(false); |
| /** should the service be cached or not */ |
| private boolean cacheTarget = false; |
| |
| public OsgiServiceFactoryBean() { |
| controller = new ExporterController(new Executor()); |
| } |
| |
| public void afterPropertiesSet() throws Exception { |
| Assert.notNull(bundleContext, "required property 'bundleContext' has not been set"); |
| |
| hasNamedBean = StringUtils.hasText(targetBeanName); |
| |
| Assert.isTrue(hasNamedBean || target != null, "Either 'targetBeanName' or 'target' properties have to be set."); |
| |
| // if we have a name, we need a bean factory |
| if (hasNamedBean) { |
| Assert.notNull(beanFactory, "Required property 'beanFactory' has not been set."); |
| } |
| |
| // initialize bean only when dealing with singletons and named beans |
| if (hasNamedBean) { |
| Assert.isTrue(beanFactory.containsBean(targetBeanName), "Cannot locate bean named '" + targetBeanName |
| + "' inside the running bean factory."); |
| |
| if (beanFactory.isSingleton(targetBeanName)) { |
| if (beanFactory instanceof ConfigurableListableBeanFactory) { |
| ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) beanFactory; |
| BeanDefinition definition = clbf.getBeanDefinition(targetBeanName); |
| if (!definition.isLazyInit()) { |
| target = beanFactory.getBean(targetBeanName); |
| targetClass = target.getClass(); |
| } |
| } |
| } |
| |
| if (targetClass == null) { |
| // lazily get the target class |
| targetClass = beanFactory.getType(targetBeanName); |
| } |
| |
| // when running inside a container, add the dependency between this bean and the target one |
| addBeanFactoryDependency(); |
| } else { |
| targetClass = target.getClass(); |
| } |
| |
| if (propertiesResolver == null) { |
| propertiesResolver = new BeanNameServicePropertiesResolver(); |
| ((BeanNameServicePropertiesResolver) propertiesResolver).setBundleContext(bundleContext); |
| } |
| |
| // sanity check |
| if (interfaces == null) { |
| if (DefaultInterfaceDetector.DISABLED.equals(interfaceDetector)) |
| throw new IllegalArgumentException( |
| "No service interface(s) specified and auto-export discovery disabled; change at least one of these properties."); |
| interfaces = new Class[0]; |
| } |
| // check visibility type |
| else { |
| if (!ServiceFactory.class.isAssignableFrom(targetClass)) { |
| for (int interfaceIndex = 0; interfaceIndex < interfaces.length; interfaceIndex++) { |
| Class<?> intf = interfaces[interfaceIndex]; |
| Assert.isAssignable(intf, targetClass, |
| "Exported service object does not implement the given interface: "); |
| } |
| } |
| } |
| // check service properties listener |
| if (serviceProperties instanceof ServicePropertiesListenerManager) { |
| propertiesListener = new PropertiesMonitor(); |
| ((ServicePropertiesListenerManager) serviceProperties).addListener(propertiesListener); |
| } |
| |
| boolean shouldRegisterAtStartup; |
| synchronized (lock) { |
| shouldRegisterAtStartup = registerAtStartup; |
| } |
| |
| resolver = |
| new LazyTargetResolver(target, beanFactory, targetBeanName, cacheTarget, getNotifier(), |
| getLazyListeners()); |
| |
| if (shouldRegisterAtStartup) { |
| registerService(); |
| } |
| } |
| |
| public void destroy() { |
| if (propertiesListener != null) { |
| if (serviceProperties instanceof ServicePropertiesListenerManager) { |
| ((ServicePropertiesListenerManager) serviceProperties).removeListener(propertiesListener); |
| } |
| propertiesListener = null; |
| } |
| |
| super.destroy(); |
| } |
| |
| private void addBeanFactoryDependency() { |
| if (beanFactory instanceof ConfigurableBeanFactory) { |
| ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; |
| if (StringUtils.hasText(beanName) && cbf.containsBean(beanName)) { |
| // no need to validate targetBeanName (already did) |
| cbf.registerDependentBean(targetBeanName, BeanFactory.FACTORY_BEAN_PREFIX + beanName); |
| cbf.registerDependentBean(targetBeanName, beanName); |
| } |
| } else { |
| log.warn("The running bean factory cannot support dependencies between beans " |
| + "- importer/exporter dependency cannot be enforced"); |
| } |
| } |
| |
| private Dictionary mergeServiceProperties(Map serviceProperties, String beanName) { |
| MapBasedDictionary props = new MapBasedDictionary(); |
| |
| // add service properties |
| if (serviceProperties != null) |
| props.putAll(serviceProperties); |
| |
| // eliminate any property that might clash with the official ones |
| props.remove(OsgiServicePropertiesResolver.BEAN_NAME_PROPERTY_KEY); |
| props.remove(OsgiServicePropertiesResolver.SPRING_DM_BEAN_NAME_PROPERTY_KEY); |
| props.remove(OsgiServicePropertiesResolver.BLUEPRINT_COMP_NAME); |
| props.remove(Constants.SERVICE_RANKING); |
| |
| // override any user property that might clash with the official ones |
| props.putAll(propertiesResolver.getServiceProperties(beanName)); |
| |
| if (ranking != 0) { |
| props.put(Constants.SERVICE_RANKING, Integer.valueOf(ranking)); |
| } |
| |
| return props; |
| } |
| |
| /** |
| * Publishes the given object as an OSGi service. It simply assembles the classes required for publishing and then |
| * delegates the actual registration to a dedicated method. |
| */ |
| @Override |
| void registerService() { |
| |
| synchronized (lock) { |
| if (serviceRegistered || !registerService) |
| return; |
| else |
| serviceRegistered = true; |
| } |
| |
| // if we have a nested bean / non-Spring managed object |
| String beanName = (!hasNamedBean ? null : targetBeanName); |
| |
| Dictionary serviceProperties = mergeServiceProperties(this.serviceProperties, beanName); |
| |
| Class<?>[] intfs = interfaces; |
| |
| // filter classes based on visibility |
| ClassLoader beanClassLoader = ClassUtils.getClassLoader(targetClass); |
| Class<?>[] autoDetectedClasses = |
| ClassUtils.getVisibleClasses(interfaceDetector.detect(targetClass), beanClassLoader); |
| |
| if (log.isTraceEnabled()) |
| log.trace("Autoexport mode [" + interfaceDetector + "] discovered on class [" + targetClass + "] classes " |
| + ObjectUtils.nullSafeToString(autoDetectedClasses)); |
| |
| // filter duplicates |
| Set<Class<?>> classes = new LinkedHashSet<Class<?>>(intfs.length + autoDetectedClasses.length); |
| |
| CollectionUtils.mergeArrayIntoCollection(intfs, classes); |
| CollectionUtils.mergeArrayIntoCollection(autoDetectedClasses, classes); |
| |
| Class<?>[] mergedClasses = (Class[]) classes.toArray(new Class[classes.size()]); |
| |
| ServiceRegistration reg = registerService(mergedClasses, serviceProperties); |
| serviceRegistration = new ServiceRegistrationDecorator(reg); |
| safeServiceRegistration.swap(serviceRegistration); |
| |
| resolver.setDecorator(serviceRegistration); |
| resolver.notifyIfPossible(); |
| } |
| |
| /** |
| * Registration method. |
| * |
| * @param classes |
| * @param serviceProperties |
| * @return the ServiceRegistration |
| */ |
| ServiceRegistration registerService(Class<?>[] classes, final Dictionary serviceProperties) { |
| Assert.notEmpty(classes, "at least one class has to be specified for exporting " |
| + "(if autoExport is enabled then maybe the object doesn't implement any interface)"); |
| |
| // create an array of classnames (used for registering the service) |
| final String[] names = ClassUtils.toStringArray(classes); |
| // sort the names in alphabetical order (eases debugging) |
| Arrays.sort(names); |
| |
| log.info("Publishing service under classes [" + ObjectUtils.nullSafeToString(names) + "]"); |
| |
| ServiceFactory serviceFactory = |
| new PublishingServiceFactory(resolver, classes, (ExportContextClassLoaderEnum.SERVICE_PROVIDER |
| .equals(contextClassLoader)), classLoader, aopClassLoader, bundleContext); |
| |
| if (isBeanBundleScoped()) |
| serviceFactory = new OsgiBundleScope.BundleScopeServiceFactory(serviceFactory); |
| |
| if (System.getSecurityManager() != null) { |
| AccessControlContext acc = SecurityUtils.getAccFrom(beanFactory); |
| final ServiceFactory serviceFactoryFinal = serviceFactory; |
| return AccessController.doPrivileged(new PrivilegedAction<ServiceRegistration>() { |
| public ServiceRegistration run() { |
| return bundleContext.registerService(names, serviceFactoryFinal, serviceProperties); |
| } |
| }, acc); |
| } else { |
| return bundleContext.registerService(names, serviceFactory, serviceProperties); |
| } |
| } |
| |
| private boolean isBeanBundleScoped() { |
| boolean bundleScoped = false; |
| // if we do have a bundle scope, use ServiceFactory decoration |
| if (targetBeanName != null) { |
| if (beanFactory instanceof ConfigurableListableBeanFactory) { |
| String beanScope = |
| ((ConfigurableListableBeanFactory) beanFactory).getMergedBeanDefinition(targetBeanName) |
| .getScope(); |
| bundleScoped = OsgiBundleScope.SCOPE_NAME.equals(beanScope); |
| } else |
| // if for some reason, the passed in BeanFactory can't be |
| // queried for scopes and we do |
| // have a bean reference, apply scoped decoration. |
| bundleScoped = true; |
| } |
| return bundleScoped; |
| } |
| |
| public void setBeanClassLoader(ClassLoader classLoader) { |
| this.classLoader = classLoader; |
| this.aopClassLoader = ClassLoaderFactory.getAopClassLoaderFor(classLoader); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p/> Returns a {@link ServiceRegistration} to the OSGi service for the target object. |
| */ |
| public ServiceRegistration getObject() throws Exception { |
| // activate |
| resolver.activate(); |
| return safeServiceRegistration; |
| } |
| |
| public Class<? extends ServiceRegistration> getObjectType() { |
| return ServiceRegistration.class; |
| } |
| |
| public boolean isSingleton() { |
| return true; |
| } |
| |
| void unregisterService() { |
| synchronized (lock) { |
| if (!serviceRegistered) |
| return; |
| else |
| serviceRegistered = false; |
| } |
| |
| unregisterService(serviceRegistration); |
| serviceRegistration = null; |
| } |
| |
| /** |
| * Unregisters (literally stops) a service. |
| * |
| * @param registration |
| */ |
| void unregisterService(ServiceRegistration registration) { |
| if (OsgiServiceUtils.unregisterService(registration)) { |
| log.info("Unregistered service [" + registration + "]"); |
| if (resolver != null) |
| resolver.setDecorator(null); |
| } |
| } |
| |
| /** |
| * Sets the context class loader management strategy to use when invoking operations on the exposed target bean. By |
| * default, {@link ExportContextClassLoaderEnum#UNMANAGED} is used. |
| * |
| * <p/> <strong>Note:</strong> Since proxying is required for context class loader manager, the target class has to |
| * meet certain criteria described in the Spring AOP documentation. In short, final classes are not supported when |
| * class enhancement is used. |
| * |
| * @param ccl context class loader strategy to use |
| * @see ExportContextClassLoaderEnum |
| */ |
| public void setExportContextClassLoader(ExportContextClassLoaderEnum ccl) { |
| Assert.notNull(ccl); |
| this.contextClassLoader = ccl; |
| } |
| |
| /** |
| * Returns the object exported as an OSGi service. |
| * |
| * @return the object exported as an OSGi service |
| */ |
| public Object getTarget() { |
| return target; |
| } |
| |
| /** |
| * Sets the given object to be export as an OSGi service. Normally used when the exported service is a nested bean |
| * or an object not managed by the Spring container. Note that the passed target instance is ignored if |
| * {@link #setTargetBeanName(String)} is used. |
| * |
| * @param target the object to be exported as an OSGi service |
| */ |
| public void setTarget(Object target) { |
| this.target = target; |
| } |
| |
| /** |
| * Returns the target bean name. |
| * |
| * @return the target object bean name |
| */ |
| public String getTargetBeanName() { |
| return targetBeanName; |
| } |
| |
| /** |
| * Sets the name of the bean managed by the Spring container, which will be exported as an OSGi service. This method |
| * is normally what most use-cases need, rather then {@link #setTarget(Object)}. |
| * |
| * @param name target bean name |
| */ |
| public void setTargetBeanName(String name) { |
| this.targetBeanName = name; |
| } |
| |
| /** |
| * Sets the strategy used for automatically publishing classes. This allows the exporter to use the target class |
| * hierarchy and/or interfaces for registering the OSGi service. By default, autoExport is disabled |
| * {@link DefaultInterfaceDetector#DISABLED}. |
| * |
| * @param detector |
| */ |
| public void setInterfaceDetector(InterfaceDetector detector) { |
| Assert.notNull(detector); |
| this.interfaceDetector = detector; |
| } |
| |
| /** |
| * Returns the properties used when exporting the target as an OSGi service. |
| * |
| * @return properties used for exporting the target |
| */ |
| public Map getServiceProperties() { |
| return serviceProperties; |
| } |
| |
| /** |
| * Sets the properties used when exposing the target as an OSGi service. If the given argument implements ( |
| * {@link ServicePropertiesChangeListener}), any updates to the properties will be reflected by the service |
| * registration. |
| * |
| * @param serviceProperties properties used for exporting the target as an OSGi service |
| */ |
| public void setServiceProperties(Map serviceProperties) { |
| this.serviceProperties = serviceProperties; |
| } |
| |
| /** |
| * Returns the OSGi ranking used when publishing the service. |
| * |
| * @return service ranking used when publishing the service |
| */ |
| public int getRanking() { |
| return ranking; |
| } |
| |
| /** |
| * Shortcut for setting the ranking property of the published service. |
| * |
| * |
| * @param ranking service ranking |
| * @see Constants#SERVICE_RANKING |
| */ |
| public void setRanking(int ranking) { |
| this.ranking = ranking; |
| } |
| |
| /** |
| * Controls whether the service actually gets published or not. This can be used by application context creators to |
| * control service creation without blocking the creation of the context |
| * |
| * @param register whether to register the service or not. The default is true. |
| */ |
| public void setRegisterService(boolean register) { |
| registerService = register; |
| // Use targetClass as a proxy for afterPropertiesSet() being called. |
| if (registerService && targetClass != null) { |
| registerService(); |
| } |
| } |
| |
| public void setBeanFactory(BeanFactory beanFactory) throws BeansException { |
| this.beanFactory = beanFactory; |
| } |
| |
| public void setBundleContext(BundleContext context) { |
| this.bundleContext = context; |
| } |
| |
| /** |
| * Returns the property resolver used for publishing the service. |
| * |
| * @return service property resolver |
| */ |
| public OsgiServicePropertiesResolver getResolver() { |
| return this.propertiesResolver; |
| } |
| |
| /** |
| * Sets the property resolver used when publishing the bean as an OSGi service. |
| * |
| * @param resolver service property resolver |
| */ |
| public void setResolver(OsgiServicePropertiesResolver resolver) { |
| this.propertiesResolver = resolver; |
| } |
| |
| /** |
| * Returns the interfaces that will be considered when exporting the target as an OSGi service. |
| * |
| * @return interfaces under which the target will be published as an OSGi service |
| */ |
| public Class<?>[] getInterfaces() { |
| return interfaces; |
| } |
| |
| /** |
| * Sets the interfaces advertised by the service.These will be advertised in the OSGi space and are considered when |
| * looking for a service. |
| * |
| * @param interfaces array of classes to advertise |
| */ |
| public void setInterfaces(Class<?>[] interfaces) { |
| this.interfaces = interfaces; |
| } |
| |
| public int getOrder() { |
| return order; |
| } |
| |
| /** |
| * Set the ordering which will apply to this class's implementation of Ordered, used when applying multiple |
| * BeanPostProcessors. <p> Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered. |
| * |
| * @param order ordering value |
| */ |
| public void setOrder(int order) { |
| this.order = order; |
| } |
| |
| /** |
| * Returns the bean name of this class when configured inside a Spring container. |
| * |
| * @return the bean name for this class |
| */ |
| public String getBeanName() { |
| return beanName; |
| } |
| |
| public void setBeanName(String name) { |
| this.beanName = name; |
| } |
| |
| /** |
| * Sets the caching of the exported target object. When enabled, the exporter will ignore the scope of the |
| * target bean and use only the first resolved instance for registration. When disabled (default), the scope of the |
| * target bean is considered and each service request, will be directed to the container. |
| * |
| * Set this option to 'true' to obtain OSGi 4.2 blueprint behaviour. |
| */ |
| public void setCacheTarget(boolean cacheTarget) { |
| this.cacheTarget = cacheTarget; |
| } |
| } |