/****************************************************************************** * 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.collection; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; 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.ImportedOsgiServiceProxy; import org.eclipse.gemini.blueprint.service.importer.OsgiServiceDependency; 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.ServiceProxyCreator; 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.util.OsgiServiceBindingUtils; import org.eclipse.gemini.blueprint.util.OsgiListenerUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; 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.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** * OSGi service dynamic collection - allows iterating while the underlying storage is being shrunk/expanded. This * collection is read-only - its content is being retrieved dynamically from the OSGi platform. * * <p/> This collection and its iterators are thread-safe. That is, multiple threads can access the collection. However, * since the collection is read-only, it cannot be modified by the client. * * @see Collection * @author Costin Leau */ public class OsgiServiceCollection implements Collection, InitializingBean, CollectionProxy, DisposableBean { private static class EventResult { static final EventResult DEFAULT = new EventResult(); Object proxy = null; // flag used for sending state events boolean shouldInformStateListeners = false; // has the collection content modified boolean collectionModified = false; } /** * Listener tracking the OSGi services which form the dynamic collection. * * @author Costin Leau */ private abstract class BaseListener implements ServiceListener { public void serviceChanged(ServiceEvent event) { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(classLoader); ServiceReference ref = event.getServiceReference(); Long serviceId = (Long) ref.getProperty(Constants.SERVICE_ID); EventResult state = null; switch (event.getType()) { case (ServiceEvent.REGISTERED): case (ServiceEvent.MODIFIED): // same as ServiceEvent.REGISTERED state = addService(serviceId, ref); // inform listeners if (state.collectionModified) { OsgiServiceBindingUtils.callListenersBind(state.proxy, ref, listeners); if (serviceRequiredAtStartup && state.shouldInformStateListeners) notifySatisfiedStateListeners(); } break; case (ServiceEvent.UNREGISTERING): state = canRemoveService(serviceId, ref); if (state.collectionModified) { OsgiServiceBindingUtils.callListenersUnbind(state.proxy, ref, listeners); state = removeService(serviceId, ref); if (serviceRequiredAtStartup && state.shouldInformStateListeners) notifyUnsatisfiedStateListeners(); } break; default: throw new IllegalArgumentException("unsupported event type:" + event); } } // OSGi swallows these exceptions so make sure we get a chance to // see them. catch (Throwable re) { if (log.isWarnEnabled()) { log.warn("serviceChanged() processing failed", re); } } finally { Thread.currentThread().setContextClassLoader(tccl); } } 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); } } } protected abstract EventResult addService(Long id, ServiceReference reference); protected abstract EventResult canRemoveService(Long serviceId, ServiceReference ref); protected abstract EventResult removeService(Long id, ServiceReference reference); } private class ServiceInstanceListener extends BaseListener { @Override protected EventResult addService(Long serviceId, ServiceReference ref) { synchronized (services) { if (!servicesIdMap.containsKey(serviceId)) { ProxyPlusCallback ppc = proxyCreator.createServiceProxy(ref); ImportedOsgiServiceProxy proxy = ppc.proxy; EventResult state = new EventResult(); state.proxy = proxy; Object value = (useServiceReferences ? proxy.getServiceReference().getTargetServiceReference() : proxy); // let the dynamic collection decide if the service // is added or not (think set, sorted set) if (services.add(value)) { state.collectionModified = true; // check if the list was empty before adding something to it state.shouldInformStateListeners = (services.size() == 1); servicesIdMap.put(serviceId, ppc); } return state; } } return EventResult.DEFAULT; } @Override protected EventResult canRemoveService(Long serviceId, ServiceReference ref) { synchronized (services) { ProxyPlusCallback ppc = servicesIdMap.get(serviceId); if (ppc != null) { EventResult state = new EventResult(); state.proxy = ppc.proxy; Object value = (useServiceReferences ? ppc.proxy.getServiceReference().getTargetServiceReference() : ppc.proxy); state.collectionModified = services.contains(value); return state; } } return EventResult.DEFAULT; } @Override protected EventResult removeService(Long serviceId, ServiceReference ref) { synchronized (services) { // remove service id / proxy association ProxyPlusCallback ppc = servicesIdMap.remove(serviceId); if (ppc != null) { EventResult state = new EventResult(); state.proxy = ppc.proxy; // remove service proxy Object value = (useServiceReferences ? ppc.proxy.getServiceReference().getTargetServiceReference() : ppc.proxy); state.collectionModified = services.remove(value); // invalidate the proxy invalidateProxy(ppc); // check if the list is empty state.shouldInformStateListeners = (services.isEmpty()); return state; } } return EventResult.DEFAULT; } } /** * Read-only iterator wrapper around the dynamic collection iterator. * * @author Costin Leau * */ protected class OsgiServiceIterator implements Iterator<Object> { // dynamic thread-safe iterator private final Iterator<Object> iter = services.iterator(); public boolean hasNext() { return iter.hasNext(); } public Object next() { return iter.next(); } public void remove() { // write operations disabled throw new UnsupportedOperationException(); } } private static final Log log = LogFactory.getLog(OsgiServiceCollection.class); private static final Log PUBLIC_LOGGER = LogFactory.getLog("org.eclipse.gemini.blueprint.service.importer." + "support.OsgiServiceCollectionProxyFactoryBean"); // map of services // NOTE: this collection is protected by the 'serviceProxies' lock. protected final Map<Long, ProxyPlusCallback> servicesIdMap = new LinkedHashMap<Long, ProxyPlusCallback>(8); /** * The dynamic collection. */ protected DynamicCollection<Object> services; private volatile boolean serviceRequiredAtStartup = true; private final Filter filter; private final BundleContext context; /** TCCL to set between calling listeners */ private final ClassLoader classLoader; /** Service proxy creator */ private final ServiceProxyCreator proxyCreator; private OsgiServiceLifecycleListener[] listeners = new OsgiServiceLifecycleListener[0]; private final ServiceListener listener; /** state listener */ private List<ImporterStateListener> stateListeners = Collections.<ImporterStateListener> emptyList(); private final Object lock = new Object(); /** dependency object */ private OsgiServiceDependency dependency; /** dependable service importer */ private Object eventSource; /** event source (importer) name */ private String sourceName; /** use references instead of instances inside the collection */ private final boolean useServiceReferences; private volatile boolean useBlueprintExceptions = false; public OsgiServiceCollection(Filter filter, BundleContext context, ClassLoader classLoader, ServiceProxyCreator proxyCreator, boolean useServiceReference) { Assert.notNull(classLoader, "ClassLoader is required"); Assert.notNull(context, "context is required"); this.filter = filter; this.context = context; this.classLoader = classLoader; this.proxyCreator = proxyCreator; this.useServiceReferences = useServiceReference; listener = new ServiceInstanceListener(); } public void afterPropertiesSet() { // create service proxies collection this.services = createInternalDynamicStorage(); dependency = new DefaultOsgiServiceDependency(sourceName, filter, serviceRequiredAtStartup); if (log.isTraceEnabled()) log.trace("Adding osgi listener for services matching [" + filter + "]"); OsgiListenerUtils.addServiceListener(context, listener, filter); synchronized (lock) { if (services.isEmpty()) { OsgiServiceBindingUtils.callListenersUnbind(null, null, listeners); } } } public void destroy() { OsgiListenerUtils.removeServiceListener(context, listener); synchronized (services) { // unwrap and destroy proxies for (Object item : services) { ServiceReference ref; if (!useServiceReferences) { ImportedOsgiServiceProxy serviceProxy = (ImportedOsgiServiceProxy) item; ref = serviceProxy.getServiceReference().getTargetServiceReference(); } else { ref = (ServiceReference) item; } // get first the destruction callback ProxyPlusCallback ppc = (ProxyPlusCallback) servicesIdMap.get((Long) ref.getProperty(Constants.SERVICE_ID)); listener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, ref)); try { ppc.destructionCallback.destroy(); } catch (Exception ex) { log.error("Exception occurred while destroying proxy " + ppc.proxy, ex); } } services.clear(); servicesIdMap.clear(); } } /** * Check to see whether at least one service is available. */ protected void mandatoryServiceCheck() { if (serviceRequiredAtStartup && services.isEmpty()) throw (useBlueprintExceptions ? BlueprintExceptionFactory.createServiceUnavailableException(filter) : new ServiceUnavailableException(filter)); } public boolean isSatisfied() { if (serviceRequiredAtStartup) return (!services.isEmpty()); else return true; } /** * Create the dynamic storage used internally. The storage <strong>has</strong> to be thread-safe. */ protected DynamicCollection<Object> createInternalDynamicStorage() { return new DynamicCollection<Object>(); } private void invalidateProxy(ProxyPlusCallback ppc) { // don't do anything (the proxy will simply thrown an exception if still in use) } public void setServiceImporter(Object importer) { this.eventSource = importer; } public void setServiceImporterName(String name) { this.sourceName = name; } public Iterator<Object> iterator() { return new OsgiServiceIterator(); } public int size() { return services.size(); } public String toString() { synchronized (services) { return services.toString(); } } // // write operations forbidden // public boolean remove(Object o) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } public boolean add(Object o) { throw new UnsupportedOperationException(); } public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } public boolean contains(Object o) { return services.contains(o); } public boolean containsAll(Collection c) { return services.containsAll(c); } public boolean isEmpty() { return size() == 0; } public Object[] toArray() { return services.toArray(); } public Object[] toArray(Object[] array) { return services.toArray(array); } /** * @param listeners The listeners to set. */ public void setListeners(OsgiServiceLifecycleListener[] listeners) { Assert.notNull(listeners); this.listeners = listeners; } public void setRequiredAtStartup(boolean serviceRequiredAtStartup) { this.serviceRequiredAtStartup = serviceRequiredAtStartup; } public void setStateListeners(List<ImporterStateListener> stateListeners) { synchronized (lock) { this.stateListeners = stateListeners; } } public void setUseBlueprintExceptions(boolean useBlueprintExceptions) { this.useBlueprintExceptions = useBlueprintExceptions; } }