org.springframework.osgi.service.importer.support.internal.aop.ServiceDynamicInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.osgi.service.importer.support.internal.aop.ServiceDynamicInterceptor.java

Source

/*
 * Copyright 2006-2009 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.osgi.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.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.osgi.service.ServiceUnavailableException;
import org.springframework.osgi.service.importer.DefaultOsgiServiceDependency;
import org.springframework.osgi.service.importer.OsgiServiceDependency;
import org.springframework.osgi.service.importer.OsgiServiceLifecycleListener;
import org.springframework.osgi.service.importer.ServiceProxyDestroyedException;
import org.springframework.osgi.service.importer.event.OsgiServiceDependencyWaitEndedEvent;
import org.springframework.osgi.service.importer.event.OsgiServiceDependencyWaitStartingEvent;
import org.springframework.osgi.service.importer.event.OsgiServiceDependencyWaitTimedOutEvent;
import org.springframework.osgi.service.importer.support.internal.dependency.ImporterStateListener;
import org.springframework.osgi.service.importer.support.internal.exception.BlueprintExceptionFactory;
import org.springframework.osgi.service.importer.support.internal.support.DefaultRetryCallback;
import org.springframework.osgi.service.importer.support.internal.support.RetryCallback;
import org.springframework.osgi.service.importer.support.internal.support.RetryTemplate;
import org.springframework.osgi.service.importer.support.internal.util.OsgiServiceBindingUtils;
import org.springframework.osgi.util.OsgiListenerUtils;
import org.springframework.osgi.util.OsgiServiceReferenceUtils;
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.springframework.osgi.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;
    }
}