/****************************************************************************** * 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; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.SortedSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceProxyCreator; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.CollectionProxy; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceCollection; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceList; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSet; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSortedList; import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSortedSet; 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.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.springframework.util.Assert; /** * OSGi service (collection) importer. This implementation creates a managed (read-only) collection of OSGi services. * The returned collection automatically handles the OSGi services dynamics. If a new service that matches the * configuration criteria appears, it will be automatically added to the collection. If a service that matches the * criteria disappears (is unregistered), it will be automatically removed from the collection. * * <p/> Due to the dynamic nature of OSGi services, the collection content can change at runtime, even during iteration. * This implementation will correctly update all the collection <code>Iterator</code>s so they reflect the collection * content. This approach (as opposed to the 'snapshot' strategy) prevents dealing with <em>dead</em> services which can * appear when imported services go down while iterating. This means that iterating while the collection is being * changed is safe. * * <p/> Note that the <code>Iterator</code> still has to be fulfilled meaning the <code>next()</code> method always obey * the result of the previous <code>hasNext()</code> invocation: * * <p/> <table border="1"> <tr> <th><code>hasNext()</code> returned value</th> <th><code>next()</code> behaviour</th> * </th> <tr> <td> <code>true</code></td> <td><em>Always</em> return a non-null value, even when the collection has * shrunk as services when away. This means returning a proxy that will throw an exception on an invocation that * requires the backing service to be present.</td> </tr> <tr> <td><code>false</code></td> <td>per <code>Iterator</code> * contract, <code>NoSuchElementException</code> is thrown. This applies even if other services are added to the * collection.</td> </tr> </table> * * <p/> Due to the dynamic nature of OSGi, <code>hasNext()</code> invocation made on the same <code>Iterator</code> can * return different values based on the services availability. However, as explained above, <code>next()</code> will * always obey the result of the last <code>hasNext()</code> method. * * <p/> <strong>Note:</strong> Even though the collection and its iterators communicate in a thread-safe manner, * iterators themselves are not thread-safe. Concurrent access on the iterators should be properly synchronized. Due to * the light nature of the iterators, consider creating a new one rather then reusing or sharing. * * * @see java.util.Iterator * @see java.util.Collection * @see java.util.List * @see java.util.Set * @see java.util.SortedSet * * @author Costin Leau */ public final class OsgiServiceCollectionProxyFactoryBean extends AbstractServiceImporterProxyFactoryBean { /** * 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 (exposedProxy == null ? true : exposedProxy.isSatisfied()); } }; private static final Log log = LogFactory.getLog(OsgiServiceCollectionProxyFactoryBean.class); /** proxy casted to a specific interface to allow specific method calls */ private CollectionProxy exposedProxy; /** proxy infrastructure hook exposed to allow clean up */ private Runnable proxyDestructionCallback; private Runnable initializationCallback; /** proxy creator */ private ServiceProxyCreator proxyCreator; private Comparator comparator; private CollectionType collectionType = CollectionType.LIST; /** greedy-proxying */ private boolean greedyProxying = false; private MemberType memberType = MemberType.SERVICE_OBJECT; /** internal listeners */ private final List<ImporterStateListener> stateListeners = Collections.synchronizedList(new ArrayList<ImporterStateListener>(4)); private final ImporterInternalActions controller; public OsgiServiceCollectionProxyFactoryBean() { controller = new ImporterController(new Executor()); } public void afterPropertiesSet() { super.afterPropertiesSet(); proxyCreator = new StaticServiceProxyCreator(getInterfaces(), getAopClassLoader(), getBeanClassLoader(), getBundleContext(), getImportContextClassLoader(), greedyProxying, isUseBlueprintExceptions()); } /** * Create the managed-collection given the existing settings. This method creates the osgi managed collection and * wraps it with an unmodifiable map to prevent exposing infrastructure methods and write access. * * @return importer proxy */ @SuppressWarnings("unchecked") @Override Object createProxy(boolean lazyProxy) { if (log.isDebugEnabled()) log.debug("Creating a multi-value/collection proxy"); OsgiServiceCollection collection; Collection delegate; BundleContext bundleContext = getBundleContext(); ClassLoader classLoader = getAopClassLoader(); Filter filter = getUnifiedFilter(); boolean useServiceReferences = MemberType.SERVICE_REFERENCE.equals(memberType); if (CollectionType.LIST.equals(collectionType)) { collection = (comparator == null ? new OsgiServiceList(filter, bundleContext, classLoader, proxyCreator, useServiceReferences) : new OsgiServiceSortedList(filter, bundleContext, classLoader, comparator, proxyCreator, useServiceReferences)); delegate = Collections.unmodifiableList((List) collection); } else if (CollectionType.SET.equals(collectionType)) { collection = (comparator == null ? new OsgiServiceSet(filter, bundleContext, classLoader, proxyCreator, useServiceReferences) : new OsgiServiceSortedSet(filter, bundleContext, classLoader, comparator, proxyCreator, useServiceReferences)); delegate = Collections.unmodifiableSet((Set) collection); } else if (CollectionType.SORTED_LIST.equals(collectionType)) { collection = new OsgiServiceSortedList(filter, bundleContext, classLoader, comparator, proxyCreator, useServiceReferences); delegate = Collections.unmodifiableList((List) collection); } else if (CollectionType.SORTED_SET.equals(collectionType)) { collection = new OsgiServiceSortedSet(filter, bundleContext, classLoader, comparator, proxyCreator, useServiceReferences); delegate = Collections.unmodifiableSortedSet((SortedSet) collection); } else { throw new IllegalArgumentException("Unknown collection type:" + collectionType); } // assign the proxy early to avoid multiple collection creation // when calling the listeners proxy = delegate; collection.setRequiredAtStartup(Availability.MANDATORY.equals(getAvailability())); collection.setListeners(getListeners()); collection.setStateListeners(stateListeners); collection.setServiceImporter(this); collection.setServiceImporterName(getBeanName()); collection.setUseBlueprintExceptions(isUseBlueprintExceptions()); // start the lookup only after the proxy has been assembled if (!lazyProxy) { collection.afterPropertiesSet(); } else { final OsgiServiceCollection col = collection; initializationCallback = new Runnable() { public void run() { col.afterPropertiesSet(); } }; } exposedProxy = collection; proxyDestructionCallback = new DisposableBeanRunnableAdapter(collection); return delegate; } @Override Runnable getProxyInitializer() { return initializationCallback; } @Override Runnable getProxyDestructionCallback() { return proxyDestructionCallback; } /** * Sets the (optional) comparator for ordering the resulting collection. The presence of a comparator will force the * FactoryBean to use a <em>sorted</em> collection even though, the specified collection type does not imply * ordering. * * <p/> Thus, instead of list a sorted list will be created and instead of a set, a sorted set. * * @see #setCollectionType(CollectionType) * * @param comparator Comparator (can be null) used for ordering the resulting collection. */ public void setComparator(Comparator comparator) { this.comparator = comparator; } /** * Sets the collection type this FactoryBean will produce. Note that if a comparator is set, a sorted collection * will be created even if the specified type is does not imply ordering. If no comparator is set but the collection * type implies ordering, the natural order of the elements will be used. * * @see #setComparator(Comparator) * @see java.lang.Comparable * @see java.util.Comparator * @see CollectionType * * @param collectionType the collection type as string using one of the values above. */ public void setCollectionType(CollectionType collectionType) { Assert.notNull(collectionType); this.collectionType = collectionType; } /** * Dictates whether <em>greedy</em> proxies are created or not (default). * * <p> Greedy proxies will proxy <b>all</b> the (visible) classes published by the imported OSGi services. This * means that the individual service proxy, might implement/extend additional classes. </p> By default, greedy * proxies are disabled (false) meaning that only the specified classes are used for generating the the imported * OSGi service proxies. * * <p/> <b>Note:</b>Greedy proxying will use the proxy mechanism dictated by this factory configuration. This means * that if JDK proxies are used, greedy proxing will consider only additional interfaces exposed by the OSGi service * and none of the extra classes. When CGLIB is used, all extra published classes (whether interfaces or * <em>non-final</em> concrete classes) will be considered. * * @param greedyProxying true if greedy proxying should be enabled, false otherwise. */ public void setGreedyProxying(boolean greedyProxying) { this.greedyProxying = greedyProxying; } /** * Sets the member type of this service collection. * * @return the collection member type */ public MemberType getMemberType() { return memberType; } /** * Sets the member type of this service collection. The collection can hold either service proxies (the default) * indicated by {@link MemberType#SERVICE_OBJECT} or service references {@link MemberType#SERVICE_REFERENCE}. * * @param type the collection member type */ public void setMemberType(MemberType type) { Assert.notNull(type); this.memberType = type; } @Override Cardinality getInternalCardinality() { return (Availability.OPTIONAL.equals(getAvailability()) ? Cardinality.C_0__N : Cardinality.C_1__N); } }