/****************************************************************************** * 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 java.util.ArrayList; import java.util.Collections; import java.util.List; import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.gemini.blueprint.service.importer.ImportedOsgiServiceProxy; import org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ProxyPlusCallback; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceDynamicInterceptor; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceProviderTCCLInterceptor; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceProxyCreator; import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterController; import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterInternalActions; import org.eclipse.gemini.blueprint.service.importer.support.internal.dependency.ImporterStateListener; import org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryTemplate; import org.eclipse.gemini.blueprint.util.internal.ClassUtils; import org.osgi.framework.ServiceReference; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.util.ObjectUtils; /** * OSGi (single) service importer. This implementation creates a managed OSGi service proxy that handles the OSGi * service dynamics. The returned proxy will select only the best matching OSGi service for the configuration criteria. * If the select service goes away (at any point in time), the proxy will automatically search for a replacement without * the user intervention. * * <p/> Note that the proxy instance remains the same and only the backing OSGi service changes. Due to the dynamic * nature of OSGi, the backing object can change during method invocations. * * @author Costin Leau * @author Adrian Colyer * @author Hal Hildebrand * */ public final class OsgiServiceProxyFactoryBean extends AbstractServiceImporterProxyFactoryBean implements ApplicationEventPublisherAware { /** * Wrapper around internal commands. * * @author Costin Leau * */ private class Executor implements ImporterInternalActions { public void addStateListener(ImporterStateListener stateListener) { stateListeners.add(stateListener); } public void removeStateListener(ImporterStateListener stateListener) { stateListeners.remove(stateListener); } public boolean isSatisfied() { if (!mandatory) return true; else return (proxy == null ? true : proxy.getServiceReference().getBundle() != null); } }; private static final Log log = LogFactory.getLog(OsgiServiceProxyFactoryBean.class); private long retryTimeout; private RetryTemplate retryTemplate; /** proxy cast to a specific interface to allow specific method calls */ private ImportedOsgiServiceProxy proxy; /** proxy infrastructure hook exposed to allow clean up */ private Runnable destructionCallback; private Runnable initializationCallback; /** application publisher */ private ApplicationEventPublisher applicationEventPublisher; /** internal listeners */ private final List<ImporterStateListener> stateListeners = Collections.synchronizedList(new ArrayList<ImporterStateListener>(4)); private final ImporterInternalActions controller; /** convenience field * */ private volatile boolean mandatory = true; private volatile boolean sticky = true; private final Object monitor = new Object(); public OsgiServiceProxyFactoryBean() { controller = new ImporterController(new Executor()); } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); mandatory = Availability.MANDATORY.equals(getAvailability()); } /** * {@inheritDoc} * * Returns a managed proxy to the best matching OSGi service. */ public Object getObject() { return super.getObject(); } Object createProxy(boolean lazyProxy) { if (log.isDebugEnabled()) log.debug("Creating a single service proxy ..."); // first create the TCCL interceptor to register its listener with the // dynamic interceptor boolean serviceTccl = ImportContextClassLoaderEnum.SERVICE_PROVIDER.equals(getImportContextClassLoader()); final ServiceProviderTCCLInterceptor tcclAdvice = (serviceTccl ? new ServiceProviderTCCLInterceptor() : null); final OsgiServiceLifecycleListener tcclListener = (serviceTccl ? tcclAdvice.new ServiceProviderTCCLListener() : null); Class<?> filterClass = ClassUtils.getParticularClass(getInterfaces()); String filterClassName = (filterClass != null ? filterClass.getName() : null); final ServiceDynamicInterceptor lookupAdvice = new ServiceDynamicInterceptor(getBundleContext(), filterClassName, getUnifiedFilter(), getAopClassLoader()); lookupAdvice.setMandatoryService(Availability.MANDATORY.equals(getAvailability())); lookupAdvice.setUseBlueprintExceptions(isUseBlueprintExceptions()); lookupAdvice.setSticky(sticky); OsgiServiceLifecycleListener[] listeners = (serviceTccl ? (OsgiServiceLifecycleListener[]) ObjectUtils.addObjectToArray(getListeners(), tcclListener) : getListeners()); lookupAdvice.setListeners(listeners); synchronized (monitor) { lookupAdvice.setRetryTimeout(retryTimeout); retryTemplate = lookupAdvice.getRetryTemplate(); } lookupAdvice.setApplicationEventPublisher(applicationEventPublisher); // add the listeners as a list since it might be updated after the proxy // has been created lookupAdvice.setStateListeners(stateListeners); lookupAdvice.setServiceImporter(this); lookupAdvice.setServiceImporterName(getBeanName()); // create a proxy creator using the existing context ServiceProxyCreator creator = new AbstractServiceProxyCreator(getInterfaces(), getAopClassLoader(), getBeanClassLoader(), getBundleContext(), getImportContextClassLoader()) { ServiceInvoker createDispatcherInterceptor(ServiceReference reference) { return lookupAdvice; } Advice createServiceProviderTCCLAdvice(ServiceReference reference) { return tcclAdvice; } }; ProxyPlusCallback proxyPlusCallback = creator.createServiceProxy(lookupAdvice.getServiceReference()); synchronized (monitor) { proxy = proxyPlusCallback.proxy; destructionCallback = new DisposableBeanRunnableAdapter(proxyPlusCallback.destructionCallback); } lookupAdvice.setProxy(proxy); // start the lookup only after the proxy has been assembled if (!lazyProxy) { lookupAdvice.afterPropertiesSet(); } else { initializationCallback = new Runnable() { public void run() { lookupAdvice.afterPropertiesSet(); } }; } return proxy; } @Override Runnable getProxyInitializer() { return initializationCallback; } @Override Runnable getProxyDestructionCallback() { synchronized (monitor) { return destructionCallback; } } /** * Add the given listener to the array but in the first position. * * @param listeners * @param listener * @return */ private OsgiServiceLifecycleListener[] addListener(OsgiServiceLifecycleListener[] listeners, OsgiServiceLifecycleListener listener) { int size = (listeners == null ? 1 : listeners.length + 1); OsgiServiceLifecycleListener[] list = new OsgiServiceLifecycleListener[size]; list[0] = listener; if (listeners != null) System.arraycopy(listeners, 0, list, 1, listeners.length); return list; } /** * Sets how long (in milliseconds) should this importer wait between failed attempts at rebinding to a service that * has been unregistered. * * <p/> It is possible to change this value after initialization (while the proxy is in place). The new values will * be used immediately by the proxy. Any in-flight waiting will be restarted using the new values. Note that if both * values are the same, no restart will be applied. * * @param timeoutInMillis Timeout to set, in milliseconds */ public void setTimeout(long timeoutInMillis) { RetryTemplate rt; synchronized (monitor) { this.retryTimeout = timeoutInMillis; rt = retryTemplate; } if (rt != null) { rt.reset(timeoutInMillis); } } /** * Returns the timeout (in milliseconds) this importer waits while trying to find a backing service. * * @return timeout in milliseconds */ public long getTimeout() { synchronized (monitor) { return retryTimeout; } } /** * Sets the stickiness of this proxy. If 'true' (default), the proxy will rebind only if the backing service is no * longer available. If 'false', the rebind will occur every time a 'better' candidate appears. A better service is * defined by having either a higher ranking or the same ranking and a lower service id. * * @param sticky sticky flag */ public void setSticky(boolean sticky) { this.sticky = sticky; } public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { synchronized (monitor) { this.applicationEventPublisher = applicationEventPublisher; } } @Override Cardinality getInternalCardinality() { return (Availability.OPTIONAL.equals(getAvailability()) ? Cardinality.C_0__1 : Cardinality.C_1__1); } }