blob: 2e49daeff423ca79e3af10fd6c1546fe6f89ffed [file] [log] [blame]
/******************************************************************************
* 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=&lt;bundle symbolic name&gt;</li>
* <li>BundleVersion=&lt;bundle version&gt;</li> <li>org.eclipse.gemini.blueprint.bean.name="&lt;bean name&gt;</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;
}
}