View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.osgi.service.importer.support;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.List;
24  import java.util.Set;
25  import java.util.SortedSet;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.osgi.framework.BundleContext;
30  import org.osgi.framework.Filter;
31  import org.springframework.beans.factory.FactoryBeanNotInitializedException;
32  import org.springframework.beans.factory.SmartFactoryBean;
33  import org.springframework.osgi.context.internal.classloader.AopClassLoaderFactory;
34  import org.springframework.osgi.service.importer.support.internal.aop.ServiceProxyCreator;
35  import org.springframework.osgi.service.importer.support.internal.collection.CollectionProxy;
36  import org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceCollection;
37  import org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceList;
38  import org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceSet;
39  import org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceSortedList;
40  import org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceSortedSet;
41  import org.springframework.osgi.service.importer.support.internal.controller.ImporterController;
42  import org.springframework.osgi.service.importer.support.internal.controller.ImporterInternalActions;
43  import org.springframework.osgi.service.importer.support.internal.dependency.ImporterStateListener;
44  import org.springframework.util.Assert;
45  
46  /**
47   * OSGi service (collection) importer. This implementation creates a managed
48   * (read-only) collection of OSGi services. The returned collection
49   * automatically handles the OSGi services dynamics. If a new service that
50   * matches the configuration criteria appears, it will be automatically added to
51   * the collection. If a service that matches the criteria disappears (is
52   * unregistered), it will be automatically removed from the collection.
53   * 
54   * <p/>Due to the dynamic nature of OSGi services, the collection content can
55   * change at runtime, even during iteration. This implementation will correctly
56   * update all the collection <code>Iterator</code>s so they reflect the
57   * collection content. This approach (as opposed to the 'snapshot' strategy)
58   * prevents dealing with <em>dead</em> services which can appear when imported
59   * services go down while iterating. This means that iterating while the
60   * collection is being changed is safe.
61   * 
62   * <p/>Note that the <code>Iterator</code> still has to be fulfilled meaning
63   * the <code>next()</code> method always obey the result of the previous
64   * <code>hasNext()</code> invocation:
65   * 
66   * <p/> <table border="1">
67   * <tr>
68   * <th><code>hasNext()</code> returned value</th>
69   * <th><code>next()</code> behaviour</th>
70   * </th>
71   * <tr>
72   * <td> <code>true</code> </td>
73   * <td><em>Always</em> return a non-null value, even when the collection has
74   * shrunk as services when away. This means returning a proxy that will throw an
75   * exception on an invocation that requires the backing service to be present.</td>
76   * </tr>
77   * <tr>
78   * <td><code>false</code></td>
79   * <td>per <code>Iterator</code> contract,
80   * <code>NoSuchElementException</code> is thrown. This applies even if other
81   * services are added to the collection.</td>
82   * </tr>
83   * </table>
84   * 
85   * <p/> Due to the dynamic nature of OSGi, <code>hasNext()</code> invocation
86   * made on the same <code>Iterator</code> can return different values based on
87   * the services availability. However, as explained above, <code>next()</code>
88   * will always obey the result of the last <code>hasNext()</code> method.
89   * 
90   * <p/><strong>Note:</strong> Even though the collection and its iterators
91   * communicate in a thread-safe manner, iterators themselves are not
92   * thread-safe. Concurrent access on the iterators should be properly
93   * synchronized. Due to the light nature of the iterators, consider creating a
94   * new one rather then reusing or sharing.
95   * 
96   * 
97   * @see java.util.Iterator
98   * @see java.util.Collection
99   * @see java.util.List
100  * @see java.util.Set
101  * @see java.util.SortedSet
102  * 
103  * @author Costin Leau
104  */
105 public final class OsgiServiceCollectionProxyFactoryBean extends AbstractServiceImporterProxyFactoryBean {
106 
107 	/**
108 	 * Wrapper around internal commands.
109 	 * 
110 	 * @author Costin Leau
111 	 * 
112 	 */
113 	private class Executor implements ImporterInternalActions {
114 
115 		public void addStateListener(ImporterStateListener stateListener) {
116 			stateListeners.add(stateListener);
117 		}
118 
119 		public void removeStateListener(ImporterStateListener stateListener) {
120 			stateListeners.remove(stateListener);
121 		}
122 
123 		public boolean isSatisfied() {
124 			return (exposedProxy == null ? true : exposedProxy.isSatisfied());
125 		}
126 	};
127 
128 
129 	private static final Log log = LogFactory.getLog(OsgiServiceCollectionProxyFactoryBean.class);
130 
131 	/**
132 	 * actual proxy - acts as a shield around the spring managed collection to
133 	 * limit the number of exposed methods
134 	 */
135 	private Collection proxy;
136 
137 	/** proxy casted to a specific interface to allow specific method calls */
138 	private CollectionProxy exposedProxy;
139 
140 	/** proxy infrastructure hook exposed to allow clean up */
141 	private Runnable proxyDestructionCallback;
142 
143 	/** proxy creator */
144 	private ServiceProxyCreator proxyCreator;
145 
146 	private Comparator comparator;
147 
148 	private CollectionType collectionType = CollectionType.LIST;
149 	/** greedy-proxying */
150 	private boolean greedyProxying = false;
151 
152 	/** internal listeners */
153 	private final List stateListeners = Collections.synchronizedList(new ArrayList(4));
154 
155 	private final ImporterInternalActions controller;
156 
157 
158 	public OsgiServiceCollectionProxyFactoryBean() {
159 		controller = new ImporterController(new Executor());
160 	}
161 
162 	public void afterPropertiesSet() {
163 		super.afterPropertiesSet();
164 
165 		// add default cardinality
166 		if (getCardinality() == null)
167 			setCardinality(Cardinality.C_1__N);
168 
169 		// create shared proxy creator ( reused for each new service
170 		// joining the collection)
171 		proxyCreator = new StaticServiceProxyCreator(getInterfaces(), getAopClassLoader(), getBundleContext(),
172 			getContextClassLoader(), greedyProxying);
173 	}
174 
175 	/**
176 	 * {@inheritDoc}
177 	 * 
178 	 * Returns the managed collection type.
179 	 */
180 	public Class getObjectType() {
181 		return (proxy != null ? proxy.getClass() : collectionType.getCollectionClass());
182 	}
183 
184 	/**
185 	 * {@inheritDoc}
186 	 * 
187 	 * Returns a managed collection that automatically handles the dynamics of
188 	 * matching OSGi services.
189 	 */
190 	public Object getObject() {
191 		return super.getObject();
192 	}
193 
194 	/**
195 	 * Create the managed-collection given the existing settings. This method
196 	 * creates the osgi managed collection and wraps it with an unmodifiable map
197 	 * to prevent exposing infrastructure methods and write access.
198 	 * 
199 	 * @return importer proxy
200 	 */
201 	Object createProxy() {
202 		if (log.isDebugEnabled())
203 			log.debug("Creating a multi-value/collection proxy");
204 
205 		OsgiServiceCollection collection;
206 		Collection delegate;
207 
208 		BundleContext bundleContext = getBundleContext();
209 		ClassLoader classLoader = getAopClassLoader();
210 		Filter filter = getUnifiedFilter();
211 
212 		if (CollectionType.LIST.equals(collectionType)) {
213 			collection = (comparator == null ? new OsgiServiceList(filter, bundleContext, classLoader, proxyCreator)
214 					: new OsgiServiceSortedList(filter, bundleContext, classLoader, comparator, proxyCreator));
215 			delegate = Collections.unmodifiableList((List) collection);
216 		}
217 		else if (CollectionType.SET.equals(collectionType)) {
218 			collection = (comparator == null ? new OsgiServiceSet(filter, bundleContext, classLoader, proxyCreator)
219 					: new OsgiServiceSortedSet(filter, bundleContext, classLoader, comparator, proxyCreator));
220 
221 			delegate = Collections.unmodifiableSet((Set) collection);
222 		}
223 		else if (CollectionType.SORTED_LIST.equals(collectionType)) {
224 			collection = new OsgiServiceSortedList(filter, bundleContext, classLoader, comparator, proxyCreator);
225 
226 			delegate = Collections.unmodifiableList((List) collection);
227 		}
228 
229 		else if (CollectionType.SORTED_SET.equals(collectionType)) {
230 			collection = new OsgiServiceSortedSet(filter, bundleContext, classLoader, comparator, proxyCreator);
231 			delegate = Collections.unmodifiableSortedSet((SortedSet) collection);
232 		}
233 
234 		else {
235 			throw new IllegalArgumentException("Unknown collection type:" + collectionType);
236 		}
237 
238 		collection.setRequiredAtStartup(getCardinality().isMandatory());
239 		collection.setListeners(getListeners());
240 		collection.setStateListeners(stateListeners);
241 		collection.setServiceImporter(this);
242 		collection.setServiceImporterName(getBeanName());
243 		collection.afterPropertiesSet();
244 
245 		proxy = delegate;
246 		exposedProxy = collection;
247 		proxyDestructionCallback = new DisposableBeanRunnableAdapter(collection);
248 
249 		return delegate;
250 	}
251 
252 	Runnable getProxyDestructionCallback() {
253 		return proxyDestructionCallback;
254 	}
255 
256 	/**
257 	 * Sets the (optional) comparator for ordering the resulting collection. The
258 	 * presence of a comparator will force the FactoryBean to use a
259 	 * <em>sorted</em> collection even though, the specified collection type
260 	 * does not imply ordering.
261 	 * 
262 	 * <p/> Thus, instead of list a sorted list will be created and instead of a
263 	 * set, a sorted set.
264 	 * 
265 	 * @see #setCollectionType(CollectionType)
266 	 * 
267 	 * @param comparator Comparator (can be null) used for ordering the
268 	 * resulting collection.
269 	 */
270 	public void setComparator(Comparator comparator) {
271 		this.comparator = comparator;
272 	}
273 
274 	/**
275 	 * Sets the collection type this FactoryBean will produce. Note that if a
276 	 * comparator is set, a sorted collection will be created even if the
277 	 * specified type is does not imply ordering. If no comparator is set but
278 	 * the collection type implies ordering, the natural order of the elements
279 	 * will be used.
280 	 * 
281 	 * @see #setComparator(Comparator)
282 	 * @see java.lang.Comparable
283 	 * @see java.util.Comparator
284 	 * @see CollectionType
285 	 * 
286 	 * @param collectionType the collection type as string using one of the
287 	 * values above.
288 	 */
289 	public void setCollectionType(CollectionType collectionType) {
290 		Assert.notNull(collectionType);
291 		this.collectionType = collectionType;
292 	}
293 
294 	/* override to check proper cardinality - x..N */
295 	/**
296 	 * {@inheritDoc}
297 	 * 
298 	 * <p/>Since this implementation creates a managed collection, only
299 	 * <em>multiple</em> cardinalities are accepted.
300 	 */
301 	public void setCardinality(Cardinality cardinality) {
302 		Assert.notNull(cardinality);
303 		Assert.isTrue(cardinality.isMultiple(), "Only multiple cardinality ('X..N') accepted");
304 		super.setCardinality(cardinality);
305 	}
306 
307 	/**
308 	 * Dictates whether <em>greedy</em> proxies are created or not (default).
309 	 * 
310 	 * <p>
311 	 * Greedy proxies will proxy <b>all</b> the (visible) classes published by
312 	 * the imported OSGi services. This means that the individual service proxy,
313 	 * might implement/extend additional classes.
314 	 * </p>
315 	 * By default, greedy proxies are disabled (false) meaning that only the
316 	 * specified classes are used for generating the the imported OSGi service
317 	 * proxies.
318 	 * 
319 	 * <p/><b>Note:</b>Greedy proxying will use the proxy mechanism dictated
320 	 * by this factory configuration. This means that if JDK proxies are used,
321 	 * greedy proxing will consider only additional interfaces exposed by the
322 	 * OSGi service and none of the extra classes. When CGLIB is used, all extra
323 	 * published classes (whether interfaces or <em>non-final</em> concrete
324 	 * classes) will be considered.
325 	 * 
326 	 * @param greedyProxying true if greedy proxying should be enabled, false
327 	 * otherwise.
328 	 */
329 	public void setGreedyProxying(boolean greedyProxying) {
330 		this.greedyProxying = greedyProxying;
331 	}
332 }