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.Collections;
21 import java.util.List;
22
23 import org.aopalliance.aop.Advice;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.osgi.framework.ServiceReference;
27 import org.springframework.beans.factory.FactoryBeanNotInitializedException;
28 import org.springframework.beans.factory.SmartFactoryBean;
29 import org.springframework.context.ApplicationEventPublisher;
30 import org.springframework.context.ApplicationEventPublisherAware;
31 import org.springframework.osgi.context.internal.classloader.AopClassLoaderFactory;
32 import org.springframework.osgi.service.importer.ImportedOsgiServiceProxy;
33 import org.springframework.osgi.service.importer.OsgiServiceLifecycleListener;
34 import org.springframework.osgi.service.importer.support.internal.aop.ProxyPlusCallback;
35 import org.springframework.osgi.service.importer.support.internal.aop.ServiceDynamicInterceptor;
36 import org.springframework.osgi.service.importer.support.internal.aop.ServiceInvoker;
37 import org.springframework.osgi.service.importer.support.internal.aop.ServiceProviderTCCLInterceptor;
38 import org.springframework.osgi.service.importer.support.internal.aop.ServiceProxyCreator;
39 import org.springframework.osgi.service.importer.support.internal.controller.ImporterController;
40 import org.springframework.osgi.service.importer.support.internal.controller.ImporterInternalActions;
41 import org.springframework.osgi.service.importer.support.internal.dependency.ImporterStateListener;
42 import org.springframework.osgi.service.importer.support.internal.support.RetryTemplate;
43 import org.springframework.util.Assert;
44 import org.springframework.util.ObjectUtils;
45
46 /**
47 * OSGi (single) service importer. This implementation creates a managed OSGi
48 * service proxy that handles the OSGi service dynamics. The returned proxy will
49 * select only the best matching OSGi service for the configuration criteria. If
50 * the select service goes away (at any point in time), the proxy will
51 * automatically search for a replacement without the user intervention.
52 *
53 * <p/> Note that the proxy instance remains the same and only the backing OSGi
54 * service changes. Due to the dynamic nature of OSGi, the backing object can
55 * change during method invocations.
56 *
57 * @author Costin Leau
58 * @author Adrian Colyer
59 * @author Hal Hildebrand
60 *
61 */
62 public final class OsgiServiceProxyFactoryBean extends AbstractServiceImporterProxyFactoryBean implements
63 ApplicationEventPublisherAware {
64
65 /**
66 * Wrapper around internal commands.
67 *
68 * @author Costin Leau
69 *
70 */
71 private class Executor implements ImporterInternalActions {
72
73 public void addStateListener(ImporterStateListener stateListener) {
74 stateListeners.add(stateListener);
75 }
76
77 public void removeStateListener(ImporterStateListener stateListener) {
78 stateListeners.remove(stateListener);
79 }
80
81 public boolean isSatisfied() {
82 if (!mandatory)
83 return true;
84 else
85 return (proxy == null ? true : proxy.getServiceReference().getBundle() != null);
86 }
87 };
88
89
90 private static final Log log = LogFactory.getLog(OsgiServiceProxyFactoryBean.class);
91
92 private RetryTemplate retryTemplate = new RetryTemplate();
93
94 /** proxy casted to a specific interface to allow specific method calls */
95 private ImportedOsgiServiceProxy proxy;
96
97 /** proxy infrastructure hook exposed to allow clean up */
98 private Runnable destructionCallback;
99
100 /** application publisher */
101 private ApplicationEventPublisher applicationEventPublisher;
102
103 /** internal listeners */
104 private final List stateListeners = Collections.synchronizedList(new ArrayList(4));
105
106 private final ImporterInternalActions controller;
107 /** convenience field * */
108 private boolean mandatory;
109
110
111 public OsgiServiceProxyFactoryBean() {
112 controller = new ImporterController(new Executor());
113 }
114
115 public void afterPropertiesSet() {
116 super.afterPropertiesSet();
117
118
119 if (getCardinality() == null)
120 setCardinality(Cardinality.C_1__1);
121 }
122
123 /**
124 * {@inheritDoc}
125 *
126 * Returns the managed proxy type. If the proxy is not created when this
127 * method is invoked, only the first interface/class will be returned.
128 */
129 public Class getObjectType() {
130 return (proxy != null ? proxy.getClass() : (ObjectUtils.isEmpty(getInterfaces()) ? Object.class
131 : getInterfaces()[0]));
132 }
133
134 /**
135 * {@inheritDoc}
136 *
137 * Returns a managed proxy to the best matching OSGi service.
138 */
139 public Object getObject() {
140 return super.getObject();
141 }
142
143 Object createProxy() {
144 if (log.isDebugEnabled())
145 log.debug("Creating a single service proxy ...");
146
147
148
149 final ServiceProviderTCCLInterceptor tcclAdvice = new ServiceProviderTCCLInterceptor();
150 final OsgiServiceLifecycleListener tcclListener = tcclAdvice.new ServiceProviderTCCLListener();
151
152 final ServiceDynamicInterceptor lookupAdvice = new ServiceDynamicInterceptor(getBundleContext(),
153 getUnifiedFilter(), getAopClassLoader());
154
155 lookupAdvice.setRequiredAtStartup(getCardinality().isMandatory());
156
157 OsgiServiceLifecycleListener[] listeners = addListener(getListeners(), tcclListener);
158
159 lookupAdvice.setListeners(listeners);
160 lookupAdvice.setRetryTemplate(new RetryTemplate(retryTemplate));
161 lookupAdvice.setApplicationEventPublisher(applicationEventPublisher);
162
163
164
165 lookupAdvice.setStateListeners(stateListeners);
166 lookupAdvice.setServiceImporter(this);
167
168
169 ServiceProxyCreator creator = new AbstractServiceProxyCreator(getInterfaces(), getAopClassLoader(),
170 getBundleContext(), getContextClassLoader()) {
171
172 ServiceInvoker createDispatcherInterceptor(ServiceReference reference) {
173 return lookupAdvice;
174 }
175
176 Advice createServiceProviderTCCLAdvice(ServiceReference reference) {
177 return tcclAdvice;
178 }
179 };
180
181 ProxyPlusCallback proxyPlusCallback = creator.createServiceProxy(lookupAdvice.getServiceReference());
182
183 proxy = proxyPlusCallback.proxy;
184 destructionCallback = new DisposableBeanRunnableAdapter(proxyPlusCallback.destructionCallback);
185
186 lookupAdvice.setProxy(proxy);
187
188 lookupAdvice.afterPropertiesSet();
189
190 return proxy;
191 }
192
193 Runnable getProxyDestructionCallback() {
194 return destructionCallback;
195 }
196
197 /**
198 * Add the given listener to the array but in the first position.
199 *
200 * @param listeners
201 * @param listener
202 * @return
203 */
204 private OsgiServiceLifecycleListener[] addListener(OsgiServiceLifecycleListener[] listeners,
205 OsgiServiceLifecycleListener listener) {
206
207 int size = (listeners == null ? 1 : listeners.length + 1);
208 OsgiServiceLifecycleListener[] list = new OsgiServiceLifecycleListener[size];
209 list[0] = listener;
210 if (listeners != null)
211 System.arraycopy(listeners, 0, list, 1, listeners.length);
212 return list;
213 }
214
215 /**
216 * Sets how many times should this importer attempt to rebind to a target
217 * service if the backing service currently used is unregistered. Default is
218 * 3 times. <p/> Changing this property after initialization is complete has
219 * no effect.
220 *
221 * @param maxRetries The maxRetries to set.
222 */
223 public void setRetryTimes(int maxRetries) {
224 this.retryTemplate.setRetryNumbers(maxRetries);
225 }
226
227 /**
228 * Returns the number of attempts to rebind a target service before giving
229 * up.
230 *
231 * @return number of retries to find a matching service before failing
232 */
233 public int getRetryTimes() {
234 return this.retryTemplate.getRetryNumbers();
235 }
236
237 /**
238 * Sets how long (in milliseconds) should this importer wait between failed
239 * attempts at rebinding to a service that has been unregistered. <p/>
240 *
241 * @param millisBetweenRetries The millisBetweenRetries to set.
242 */
243 public void setTimeout(long millisBetweenRetries) {
244 this.retryTemplate.setWaitTime(millisBetweenRetries);
245 }
246
247 /**
248 * Returns the timeout (in milliseconds) this importer waits while trying to
249 * find a backing service.
250 *
251 * @return timeout in milliseconds
252 */
253 public long getTimeout() {
254 return this.retryTemplate.getWaitTime();
255 }
256
257
258 /**
259 * {@inheritDoc}
260 *
261 * <p/>Since this implementation creates a managed proxy, only
262 * <em>single</em> cardinalities are accepted.
263 */
264 public void setCardinality(Cardinality cardinality) {
265 Assert.notNull(cardinality);
266 Assert.isTrue(cardinality.isSingle(), "only singular cardinality ('X..1') accepted");
267 super.setCardinality(cardinality);
268 this.mandatory = cardinality.isMandatory();
269 }
270
271 public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
272 this.applicationEventPublisher = applicationEventPublisher;
273 }
274 }