blob: 37f654042283f73adef0d5fb7d299628d6ac53a0 [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.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() {
return !mandatory || (proxy == null || 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 ? 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);
}
}