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.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 		// add default cardinality
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 		// first create the TCCL interceptor to register its listener with the
148 		// dynamic interceptor
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 		// add the listeners as a list since it might be updated after the proxy
164 		// has been created
165 		lookupAdvice.setStateListeners(stateListeners);
166 		lookupAdvice.setServiceImporter(this);
167 
168 		// create a proxy creator using the existing context
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 		// start the lookup only after the proxy has been assembled
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 	/* override to check proper cardinality - x..1 */
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 }