/****************************************************************************** * 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.importer.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.service.exporter.OsgiServicePropertiesResolver; import org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener; import org.eclipse.gemini.blueprint.util.OsgiFilterUtils; import org.eclipse.gemini.blueprint.util.internal.ClassUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Base class for importing OSGi services. Provides the common properties and contracts between importers. * * @author Costin Leau * @author Adrian Colyer * @author Hal Hildebrand */ public abstract class AbstractOsgiServiceImportFactoryBean implements FactoryBean<Object>, InitializingBean, DisposableBean, BundleContextAware, BeanClassLoaderAware, BeanNameAware { private static final Log log = LogFactory.getLog(AbstractOsgiServiceImportFactoryBean.class); /** context classloader */ private ClassLoader classLoader; private BundleContext bundleContext; private ImportContextClassLoaderEnum contextClassLoader = ImportContextClassLoaderEnum.CLIENT; // not required to be an interface, but usually should be... private Class<?>[] interfaces; // filter used to narrow service matches, may be null private String filter; // Cumulated filter string between the specified classes/interfaces and the // given filter private Filter unifiedFilter; // service lifecycle listener private OsgiServiceLifecycleListener[] listeners; /** Service Bean property of the OSGi service * */ private String serviceBeanName; private Availability availability = Availability.MANDATORY; /** bean name */ private String beanName = ""; public void afterPropertiesSet() { Assert.notNull(this.bundleContext, "Required 'bundleContext' property was not set."); Assert.notNull(classLoader, "Required 'classLoader' property was not set."); Assert.isTrue(!ObjectUtils.isEmpty(interfaces) || StringUtils.hasText(filter) || StringUtils.hasText(serviceBeanName), "At least the interface or filter or service name needs to be defined to import an OSGi service"); if (ObjectUtils.isEmpty(interfaces)) { log.warn("OSGi importer [" + beanName + "] definition contains no interfaces: " + "all invocations will be executed on the proxy and not on the backing service"); } // validate specified classes Assert.isTrue(!ClassUtils.containsUnrelatedClasses(interfaces), "More then one concrete class specified; cannot create proxy."); this.listeners = (listeners == null ? new OsgiServiceLifecycleListener[0] : listeners); this.interfaces = (interfaces == null ? new Class<?>[0] : interfaces); this.filter = (StringUtils.hasText(filter) ? filter : ""); getUnifiedFilter(); // eager initialization of the cache to catch filter errors } /** * Assembles the configuration properties into one unified OSGi filter. Note that this implementation creates the * filter on the first call and caches it afterwards. * * @return unified filter based on this factory bean configuration */ public Filter getUnifiedFilter() { if (unifiedFilter != null) { return unifiedFilter; } String filterWithClasses = (!ObjectUtils.isEmpty(interfaces) ? OsgiFilterUtils.unifyFilter(interfaces, filter) : filter); boolean trace = log.isTraceEnabled(); if (trace) log.trace("Unified classes=" + ObjectUtils.nullSafeToString(interfaces) + " and filter=[" + filter + "] in=[" + filterWithClasses + "]"); // add the serviceBeanName/Blueprint component name constraint String nameFilter; if (StringUtils.hasText(serviceBeanName)) { StringBuilder nsFilter = new StringBuilder("(|("); nsFilter.append(OsgiServicePropertiesResolver.BEAN_NAME_PROPERTY_KEY); nsFilter.append("="); nsFilter.append(serviceBeanName); nsFilter.append(")("); nsFilter.append(OsgiServicePropertiesResolver.SPRING_DM_BEAN_NAME_PROPERTY_KEY); nsFilter.append("="); nsFilter.append(serviceBeanName); nsFilter.append(")("); nsFilter.append(OsgiServicePropertiesResolver.BLUEPRINT_COMP_NAME); nsFilter.append("="); nsFilter.append(serviceBeanName); nsFilter.append("))"); nameFilter = nsFilter.toString(); } else { nameFilter = null; } String filterWithServiceBeanName = filterWithClasses; if (nameFilter != null) { StringBuilder finalFilter = new StringBuilder(); finalFilter.append("(&"); finalFilter.append(filterWithClasses); finalFilter.append(nameFilter); finalFilter.append(")"); filterWithServiceBeanName = finalFilter.toString(); } if (trace) log.trace("Unified serviceBeanName [" + ObjectUtils.nullSafeToString(serviceBeanName) + "] and filter=[" + filterWithClasses + "] in=[" + filterWithServiceBeanName + "]"); // create (which implies validation) the actual filter unifiedFilter = OsgiFilterUtils.createFilter(filterWithServiceBeanName); return unifiedFilter; } /** * Sets the classes that the imported service advertises. * * @param interfaces array of advertised classes. */ public void setInterfaces(Class<?>[] interfaces) { this.interfaces = interfaces; } /** * Sets the thread context class loader management strategy to use for services imported by this service. By default * {@link ImportContextClassLoader#CLIENT} is used. * * @param contextClassLoader import context class loader management strategy * @see ImportContextClassLoader * @deprecated As of Spring DM 2.0, replaced by {@link #setImportContextClassLoader(ImportContextClassLoaderEnum))} */ public void setContextClassLoader(ImportContextClassLoader contextClassLoader) { Assert.notNull(contextClassLoader); this.contextClassLoader = contextClassLoader.getImportContextClassLoaderEnum(); } /** * Sets the thread context class loader management strategy to use for services imported by this service. By default * {@link ImportContextClassLoaderEnum#CLIENT} is used. * * @param contextClassLoader import context class loader management strategy * @see ImportContextClassLoader */ public void setImportContextClassLoader(ImportContextClassLoaderEnum contextClassLoader) { Assert.notNull(contextClassLoader); this.contextClassLoader = contextClassLoader; } public void setBundleContext(BundleContext context) { this.bundleContext = context; } /** * Sets the OSGi service filter. The filter will be concatenated with the rest of the configuration properties * specified (such as interfaces) so there is no need to include them in the filter. * * @param filter OSGi filter describing the importing OSGi service */ public void setFilter(String filter) { this.filter = filter; } /** * Sets the lifecycle listeners interested in receiving events for this importer. * * @param listeners importer listeners */ public void setListeners(OsgiServiceLifecycleListener[] listeners) { this.listeners = listeners; } /** * Sets the OSGi service bean name. This setting should be normally used when the imported service has been exported * by Spring DM exporter. You may specify additional filtering criteria if needed (using the filter property) but * this is not required. * * @param serviceBeanName importer service bean name */ public void setServiceBeanName(String serviceBeanName) { this.serviceBeanName = serviceBeanName; } /** * {@inheritDoc} * * This method is called automatically by the container. */ public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Returns the class loader used by this FactoryBean. * * @return factory bean class loader */ public ClassLoader getBeanClassLoader() { return classLoader; } /** * Returns the bundleContext used by this FactoryBean. * * @return factory bean class loader */ public BundleContext getBundleContext() { return bundleContext; } /** * Returns the interfaces used for discovering the imported service(s). * * @return interfaces advertised by services in the OSGi space */ public Class<?>[] getInterfaces() { return interfaces; } /** * Returns the filter describing the imported service(s). * * @return filter describing the imported service(s) */ public String getFilter() { return filter; } /** * Returns the listeners interested in receiving events for this importer. * * @return lifecycle listeners used by this importer */ public OsgiServiceLifecycleListener[] getListeners() { return listeners; } /** * Returns the context class loader management strategy. * * @return the context class loader management strategy * @deprecated As of Spring DM 2.0, replaced by {@link #getImportContextClassLoader()} */ public ImportContextClassLoader getContextClassLoader() { return ImportContextClassLoader.getImportContextClassLoader(contextClassLoader); } /** * Returns the context class loader management strategy. * * @return the context class loader management strategy */ public ImportContextClassLoaderEnum getImportContextClassLoader() { return contextClassLoader; } /** * Returns the cardinality used by this importer. * * @return importer cardinality * @deprecated As of Spring DM 2.0, replaced by {@link #getAvailability()} */ public Cardinality getCardinality() { return getInternalCardinality(); } abstract Cardinality getInternalCardinality(); public Availability getAvailability() { return availability; } /** * Sets the importer cardinality (0..1, 1..1, 0..N, or 1..N). Default is 1..X. * * @param cardinality importer cardinality. * @deprecated As of Spring DM 2.0, replaced by {@link #setAvailability(Availability)} */ public void setCardinality(Cardinality cardinality) { Assert.notNull(cardinality); this.availability = cardinality.getAvailability(); } /** * Sets the importer availability. Default is mandatory ({@link Availability#MANDATORY} * @param availability */ public void setAvailability(Availability availability) { Assert.notNull(availability); this.availability = availability; } /** * Returns the bean name associated with the instance of this class (when running inside the Spring container). * * @return component bean name */ public String getBeanName() { return beanName; } public void setBeanName(String name) { beanName = name; } }