/****************************************************************************** * 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; } }