1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
166 if (getCardinality() == null)
167 setCardinality(Cardinality.C_1__N);
168
169
170
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
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 }