/******************************************************************************
* Copyright (c) 2006, 2010 VMware 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.
*****************************************************************************/
package org.eclipse.gemini.blueprint.service.importer.support.internal.aop;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.service.ServiceUnavailableException;
import org.eclipse.gemini.blueprint.service.importer.DefaultOsgiServiceDependency;
import org.eclipse.gemini.blueprint.service.importer.OsgiServiceDependency;
import org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener;
import org.eclipse.gemini.blueprint.service.importer.ServiceProxyDestroyedException;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitEndedEvent;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitStartingEvent;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitTimedOutEvent;
import org.eclipse.gemini.blueprint.service.importer.support.internal.dependency.ImporterStateListener;
import org.eclipse.gemini.blueprint.service.importer.support.internal.exception.BlueprintExceptionFactory;
import org.eclipse.gemini.blueprint.service.importer.support.internal.support.DefaultRetryCallback;
import org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryCallback;
import org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryTemplate;
import org.eclipse.gemini.blueprint.service.importer.support.internal.util.OsgiServiceBindingUtils;
import org.eclipse.gemini.blueprint.util.OsgiListenerUtils;
import org.eclipse.gemini.blueprint.util.OsgiServiceReferenceUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Interceptor adding dynamic behaviour for unary service (..1 cardinality). It will look for a service using the given
* filter, retrying if the service is down or unavailable. Will dynamically rebound a new service, if one is available
* with a higher service ranking and the interceptor is non sticky.
*
* <p/> In case no service is available, it will throw an exception.
*
* <p/> <strong>Note</strong>: this is a stateful interceptor and should not be shared.
*
* @author Costin Leau
*/
public class ServiceDynamicInterceptor extends ServiceInvoker implements InitializingBean,
ApplicationEventPublisherAware {
/**
* Override the default implementation to plug in event notification.
*
* @author Costin Leau
*
*/
private class EventSenderRetryTemplate extends RetryTemplate {
public EventSenderRetryTemplate(long waitTime) {
super(waitTime, lock);
}
public EventSenderRetryTemplate() {
super(lock);
}
protected void callbackFailed(long stop) {
publishEvent(new OsgiServiceDependencyWaitTimedOutEvent(eventSource, dependency, stop));
}
protected void callbackSucceeded(long stop) {
publishEvent(new OsgiServiceDependencyWaitEndedEvent(eventSource, dependency, stop));
}
protected void onMissingTarget() {
// send event
publishEvent(new OsgiServiceDependencyWaitStartingEvent(eventSource, dependency, this.getWaitTime()));
}
}
private class ServiceLookUpCallback extends DefaultRetryCallback<Object> {
public Object doWithRetry() {
// before checking for a service, check whether the proxy is still valid
if (destroyed && !isDuringDestruction) {
throw new ServiceProxyDestroyedException();
}
return (holder != null ? holder.getService() : null);
}
}
private class ServiceReferenceLookUpCallback extends DefaultRetryCallback<ServiceReference> {
public ServiceReference doWithRetry() {
// before checking for a service, check whether the proxy is still valid
if (destroyed && !isDuringDestruction) {
throw new ServiceProxyDestroyedException();
}
return (holder != null ? holder.getReference() : null);
}
}
/**
* Listener tracking the OSGi services which form the dynamic reference.
*/
// NOTE: while the listener here seems to share the same functionality as
// the one in ServiceCollection in reality there are a big number of
// differences in them - for example this one supports rebind
// while the collection does not.
//
// the only common part is the TCCL handling before calling the listeners.
private class Listener implements ServiceListener {
public void serviceChanged(ServiceEvent event) {
boolean hasSecurity = (System.getSecurityManager() != null);
ClassLoader tccl = null;
if (hasSecurity) {
tccl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
return cl;
}
});
} else {
tccl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
}
try {
ServiceReference ref = event.getServiceReference();
boolean debug = log.isDebugEnabled();
boolean publicDebug = PUBLIC_LOGGER.isDebugEnabled();
switch (event.getType()) {
case (ServiceEvent.REGISTERED):
// same as ServiceEvent.REGISTERED
case (ServiceEvent.MODIFIED): {
// flag indicating if the service is bound or rebound
boolean servicePresent = (holder != null);
if (updateWrapperIfNecessary(ref)) {
// inform listeners
OsgiServiceBindingUtils.callListenersBind(proxy, ref, listeners);
// we have a bind
if (!servicePresent) {
notifySatisfiedStateListeners();
}
}
break;
}
case (ServiceEvent.UNREGISTERING): {
boolean serviceRemoved = false;
//
// used if the service goes down and there is no replacement
//
// since the listeners will require a valid proxy, the invalidation has to happen *after* calling
// the listeners
//
ReferenceHolder oldHolder = holder;
// remove service
if (holder != null && holder.equals(ref)) {
serviceRemoved = true;
holder = null;
}
ServiceReference newReference = null;
boolean isDestroyed = destroyed;
// discover a new reference only if we are still running
if (!isDestroyed) {
newReference =
OsgiServiceReferenceUtils.getServiceReference(bundleContext, filterClassName,
(filter == null ? null : filter.toString()));
// we have a rebind (a new service was bound)
// so another candidate has to be searched from the existing candidates
// - as they are alive already, we have to send an event for them ourselves
// MODIFIED will be used for clarity
if (newReference != null) {
// update the listeners (through a MODIFIED event
serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, newReference));
}
}
// if no new reference was found and the service was indeed removed (it was bound to the
// interceptor) then do an unbind
if (newReference == null && serviceRemoved) {
// reuse the old service until the listeners are notified
holder = oldHolder;
// inform listeners
OsgiServiceBindingUtils.callListenersUnbind(proxy, ref, listeners);
holder = null;
if (debug || publicDebug) {
String message = "Service reference [" + ref + "] was unregistered";
if (serviceRemoved) {
message += " and unbound from the service proxy";
} else {
message += " but did not affect the service proxy";
}
if (debug)
log.debug(message);
if (publicDebug)
PUBLIC_LOGGER.debug(message);
}
// update internal state listeners (unsatisfied event)
notifyUnsatisfiedStateListeners();
}
break;
}
default:
throw new IllegalArgumentException("unsupported event type");
}
} catch (Throwable e) {
// The framework will swallow these exceptions without logging,
// so log them here
log.fatal("Exception during service event handling", e);
} finally {
final ClassLoader finalTccl = tccl;
if (hasSecurity) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
Thread.currentThread().setContextClassLoader(finalTccl);
return null;
}
});
}
else {
Thread.currentThread().setContextClassLoader(finalTccl);
}
}
}
private void notifySatisfiedStateListeners() {
synchronized (stateListeners) {
for (ImporterStateListener stateListener : stateListeners) {
stateListener.importerSatisfied(eventSource, dependency);
}
}
}
private void notifyUnsatisfiedStateListeners() {
synchronized (stateListeners) {
for (ImporterStateListener stateListener : stateListeners) {
stateListener.importerUnsatisfied(eventSource, dependency);
}
}
}
private boolean updateWrapperIfNecessary(ServiceReference ref) {
boolean updated = false;
try {
if (holder == null || (!sticky && holder.isWorseThen(ref))) {
updated = true;
updateReferenceHolders(ref);
}
synchronized (lock) {
lock.notifyAll();
}
return updated;
} finally {
boolean debug = log.isDebugEnabled();
boolean publicDebug = PUBLIC_LOGGER.isDebugEnabled();
if (debug || publicDebug) {
String message = "Service reference [" + ref + "]";
if (updated)
message += " bound to proxy";
else
message += " not bound to proxy";
if (debug)
log.debug(message);
if (publicDebug)
PUBLIC_LOGGER.debug(message);
}
}
}
/**
* Update internal holders for the backing ServiceReference.
*
* @param ref
*/
private void updateReferenceHolders(ServiceReference ref) {
holder = new ReferenceHolder(ref, bundleContext);
referenceDelegate.swapDelegates(ref);
}
}
private static final int hashCode = ServiceDynamicInterceptor.class.hashCode() * 13;
/** public logger */
private static final Log PUBLIC_LOGGER =
LogFactory.getLog("org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean");
private final BundleContext bundleContext;
private final String filterClassName;
private final Filter filter;
/** TCCL to set when calling listeners */
private final ClassLoader classLoader;
private final SwappingServiceReferenceProxy referenceDelegate;
/** event listener */
private final ServiceListener listener;
/** mandatory flag */
private boolean mandatoryService = true;
/** flag indicating whether the destruction has started or not */
private boolean isDuringDestruction = false;
/** flag indicating whether the proxy is already destroyed or not */
private volatile boolean destroyed = false;
/** private lock */
/**
* used for reading/setting property and sending notifications between the event listener and any threads waiting
* for an OSGi service to appear
*/
private final Object lock = new Object();
/** service reference/service holder */
private volatile ReferenceHolder holder;
/** retry template */
private final RetryTemplate retryTemplate = new EventSenderRetryTemplate();
/** retry callback */
private final RetryCallback<Object> retryCallback = new ServiceLookUpCallback();
/** dependable service importer */
private Object eventSource;
/** event source (importer) name */
private String sourceName;
/** listener that need to be informed of bind/rebind/unbind */
private OsgiServiceLifecycleListener[] listeners = new OsgiServiceLifecycleListener[0];
/** reference to the created proxy passed to the listeners */
private Object proxy;
/** event publisher */
private ApplicationEventPublisher applicationEventPublisher;
/** dependency object */
private OsgiServiceDependency dependency;
/** internal state listeners */
private List<ImporterStateListener> stateListeners = Collections.emptyList();
/** standard exception flag */
private boolean useBlueprintExceptions = false;
private boolean sticky = false;
public ServiceDynamicInterceptor(BundleContext context, String filterClassName, Filter filter,
ClassLoader classLoader) {
this.bundleContext = context;
this.filterClassName = filterClassName;
this.filter = filter;
this.classLoader = classLoader;
referenceDelegate = new SwappingServiceReferenceProxy();
listener = new Listener();
}
public Object getTarget() {
Object target = lookupService();
// nothing found
if (target == null) {
throw (useBlueprintExceptions ? BlueprintExceptionFactory.createServiceUnavailableException(filter)
: new ServiceUnavailableException(filter));
}
return target;
}
public ServiceReference getTargetReference() {
ServiceReference reference = lookupServiceReference();
// nothing found
if (reference == null) {
throw (useBlueprintExceptions ? BlueprintExceptionFactory.createServiceUnavailableException(filter)
: new ServiceUnavailableException(filter));
}
return reference;
}
/**
* Looks the service by waiting the service to appear. Note this method should use the same lock as the listener
* handling the service reference.
*/
private Object lookupService() {
synchronized (lock) {
return retryTemplate.execute(retryCallback);
}
}
/**
* Looks for the service reference to appear.
*
* @return
*/
private ServiceReference lookupServiceReference() {
synchronized (lock) {
return retryTemplate.execute(new ServiceReferenceLookUpCallback());
}
}
private void publishEvent(ApplicationEvent event) {
if (applicationEventPublisher != null) {
if (log.isTraceEnabled())
log.trace("Publishing event through publisher " + applicationEventPublisher);
try {
applicationEventPublisher.publishEvent(event);
} catch (IllegalStateException ise) {
log.debug("Event " + event + " not published as the publisher is not initialized - "
+ "usually this is caused by eager initialization of the importers by post processing", ise);
}
} else if (log.isTraceEnabled())
log.trace("No application event publisher set; no events will be published");
}
public void afterPropertiesSet() {
Assert.notNull(proxy);
Assert.notNull(eventSource);
boolean debug = log.isDebugEnabled();
dependency = new DefaultOsgiServiceDependency(sourceName, filter, mandatoryService);
if (debug)
log.debug("Adding OSGi mandatoryListeners for services matching [" + filter + "]");
OsgiListenerUtils.addSingleServiceListener(bundleContext, listener, filter);
// inform listeners (in case no service is available)
synchronized (lock) {
if (referenceDelegate.getTargetServiceReference() == null) {
OsgiServiceBindingUtils.callListenersUnbind(null, null, listeners);
}
}
}
public void destroy() {
OsgiListenerUtils.removeServiceListener(bundleContext, listener);
ServiceReference ref = null;
synchronized (lock) {
// set this flag first to make sure no rebind is done
destroyed = true;
isDuringDestruction = true;
if (holder != null) {
ref = holder.getReference();
// send unregistration event to the listener
listener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));
}
/** destruction process has ended */
isDuringDestruction = false;
// notify also any proxies that still wait on the service
lock.notifyAll();
}
// unget the service (help sorting out the bundles during shutdown)
if (ref != null) {
try {
bundleContext.ungetService(ref);
} catch (IllegalStateException ex) {
// it's okay if the context is invalid
}
}
}
/**
* {@inheritDoc}
*
* This particular interceptor returns a delegated service reference so that callers can keep the reference even if
* the underlying target service reference changes in time.
*/
public ServiceReference getServiceReference() {
return referenceDelegate;
}
public void setRetryTimeout(long timeout) {
retryTemplate.reset(timeout);
}
public RetryTemplate getRetryTemplate() {
return retryTemplate;
}
public OsgiServiceLifecycleListener[] getListeners() {
return listeners;
}
public void setListeners(OsgiServiceLifecycleListener[] listeners) {
this.listeners = listeners;
}
public void setServiceImporter(Object importer) {
this.eventSource = importer;
}
public void setServiceImporterName(String name) {
this.sourceName = name;
}
public void setMandatoryService(boolean mandatoryService) {
this.mandatoryService = mandatoryService;
}
public void setProxy(Object proxy) {
this.proxy = proxy;
}
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/** Internal state listeners */
public void setStateListeners(List stateListeners) {
this.stateListeners = stateListeners;
}
public void setUseBlueprintExceptions(boolean useBlueprintExceptions) {
this.useBlueprintExceptions = useBlueprintExceptions;
}
public void setSticky(boolean sticky) {
this.sticky = sticky;
}
public boolean equals(Object other) {
if (this == other)
return true;
if (other instanceof ServiceDynamicInterceptor) {
ServiceDynamicInterceptor oth = (ServiceDynamicInterceptor) other;
return (mandatoryService == oth.mandatoryService && ObjectUtils.nullSafeEquals(holder, oth.holder)
&& ObjectUtils.nullSafeEquals(filter, oth.filter) && ObjectUtils.nullSafeEquals(retryTemplate,
oth.retryTemplate));
} else
return false;
}
public int hashCode() {
return hashCode;
}
}