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.context.support;
18  
19  import org.springframework.beans.BeansException;
20  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
21  import org.springframework.context.ApplicationContext;
22  import org.springframework.context.ApplicationContextException;
23  import org.springframework.context.event.ApplicationEventMulticaster;
24  import org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext;
25  import org.springframework.osgi.context.OsgiBundleApplicationContextExecutor;
26  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticaster;
27  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticasterAdapter;
28  import org.springframework.osgi.context.event.OsgiBundleContextFailedEvent;
29  import org.springframework.osgi.context.event.OsgiBundleContextRefreshedEvent;
30  import org.springframework.osgi.util.OsgiBundleUtils;
31  import org.springframework.osgi.util.OsgiStringUtils;
32  import org.springframework.osgi.util.internal.ClassUtils;
33  import org.springframework.util.Assert;
34  import org.springframework.util.ObjectUtils;
35  
36  /**
37   * OSGi-specific application context that delegates the execution of its life
38   * cycle methods to a different class. The main reason behind this is to
39   * <em>break</em> the startup of the application context in steps that can be
40   * executed asynchronously.
41   * 
42   * <p/> The {@link #refresh()} and {@link #close()} methods delegate their
43   * execution to an {@link OsgiBundleApplicationContextExecutor} class that
44   * chooses how to call the lifecycle methods.
45   * 
46   * <p/> One can still call the 'traditional' lifecycle methods through
47   * {@link #normalRefresh()} and {@link #normalClose()}.
48   * 
49   * @see DelegatedExecutionOsgiBundleApplicationContext
50   * 
51   * @author Costin Leau
52   */
53  public abstract class AbstractDelegatedExecutionApplicationContext extends AbstractOsgiBundleApplicationContext
54  		implements DelegatedExecutionOsgiBundleApplicationContext {
55  
56  	/**
57  	 * Executor that offers the traditional way of <code>refreshing</code>/<code>closing</code>
58  	 * of an ApplicationContext (no conditions have to be met and the refresh
59  	 * happens in only one step).
60  	 * 
61  	 * @author Costin Leau
62  	 */
63  	private static class NoDependenciesWaitRefreshExecutor implements OsgiBundleApplicationContextExecutor {
64  
65  		private final DelegatedExecutionOsgiBundleApplicationContext context;
66  
67  
68  		private NoDependenciesWaitRefreshExecutor(DelegatedExecutionOsgiBundleApplicationContext ctx) {
69  			context = ctx;
70  		}
71  
72  		public void refresh() throws BeansException, IllegalStateException {
73  			context.normalRefresh();
74  		}
75  
76  		public void close() {
77  			context.normalClose();
78  		}
79  	}
80  
81  
82  	/** Default executor */
83  	private OsgiBundleApplicationContextExecutor executor = new NoDependenciesWaitRefreshExecutor(this);
84  
85  	/** this context monitor */
86  	private final Object contextMonitor = new Object();
87  
88  	/** monitor for available flag */
89  	private final Object availableMonitor = new Object();
90  
91  	private boolean available = false;
92  
93  	/** delegated multicaster */
94  	private OsgiBundleApplicationContextEventMulticaster delegatedMulticaster;
95  
96  	private ContextClassLoaderProvider cclProvider;
97  
98  
99  	/**
100 	 * 
101 	 * Create a new AbstractDelegatedExecutionApplicationContext with no parent.
102 	 * 
103 	 */
104 	public AbstractDelegatedExecutionApplicationContext() {
105 		super();
106 	}
107 
108 	/**
109 	 * Create a new AbstractDelegatedExecutionApplicationContext with the given
110 	 * parent context.
111 	 * 
112 	 * @param parent the parent context
113 	 */
114 	public AbstractDelegatedExecutionApplicationContext(ApplicationContext parent) {
115 		super(parent);
116 	}
117 
118 	boolean isAvailable() {
119 		synchronized (availableMonitor) {
120 			return available;
121 		}
122 	}
123 
124 	/**
125 	 * Delegate execution of refresh method to a third party. This allows
126 	 * breaking the refresh process into several small pieces providing
127 	 * continuation-like behavior or completion of the refresh method on several
128 	 * threads, in a asynch manner.
129 	 * 
130 	 * By default, the refresh method in executed in <em>one go</em> (normal
131 	 * behavior).
132 	 * 
133 	 * {@inheritDoc}
134 	 */
135 	public void refresh() throws BeansException, IllegalStateException {
136 		executor.refresh();
137 	}
138 
139 	public void normalRefresh() {
140 		Assert.notNull(getBundleContext(), "bundle context should be set before refreshing the application context");
141 
142 		Thread currentThread = Thread.currentThread();
143 		ClassLoader oldTCCL = currentThread.getContextClassLoader();
144 
145 		try {
146 			currentThread.setContextClassLoader(contextClassLoaderProvider().getContextClassLoader());
147 			try {
148 				super.refresh();
149 				sendRefreshedEvent();
150 			}
151 			catch (Throwable th) {
152 				logger.error("Refresh error", th);
153 				sendFailedEvent(th);
154 				// propagate exception to the caller
155 				// rethrow the problem w/o rewrapping
156 				if (th instanceof RuntimeException) {
157 					throw (RuntimeException) th;
158 				}
159 				else {
160 					throw (Error) th;
161 				}
162 			}
163 		}
164 		finally {
165 			currentThread.setContextClassLoader(oldTCCL);
166 		}
167 	}
168 
169 	public void normalClose() {
170 		Thread currentThread = Thread.currentThread();
171 		ClassLoader oldTCCL = currentThread.getContextClassLoader();
172 
173 		try {
174 			currentThread.setContextClassLoader(contextClassLoaderProvider().getContextClassLoader());
175 			super.doClose();
176 		}
177 		finally {
178 			currentThread.setContextClassLoader(oldTCCL);
179 		}
180 	}
181 
182 	// Adds behaviour for isAvailable flag.
183 	protected void doClose() {
184 		synchronized (availableMonitor) {
185 			available = false;
186 		}
187 		executor.close();
188 	}
189 
190 	public void startRefresh() {
191 
192 		// check concurrent collection (which are mandatory)
193 		if (!ClassUtils.concurrentLibAvailable())
194 			throw new IllegalStateException(
195 				"JVM 5+ or backport-concurrent library (for JVM 1.4) required; see the FAQ for more details");
196 
197 		Thread thread = Thread.currentThread();
198 		ClassLoader oldTCCL = thread.getContextClassLoader();
199 
200 		try {
201 			synchronized (contextMonitor) {
202 				thread.setContextClassLoader(contextClassLoaderProvider().getContextClassLoader());
203 
204 				if (ObjectUtils.isEmpty(getConfigLocations())) {
205 					setConfigLocations(getDefaultConfigLocations());
206 				}
207 				if (!OsgiBundleUtils.isBundleActive(getBundle())) {
208 					throw new ApplicationContextException("Unable to refresh application context: bundle is "
209 							+ OsgiStringUtils.bundleStateAsString(getBundle()));
210 				}
211 
212 				ConfigurableListableBeanFactory beanFactory = null;
213 				// Prepare this context for refreshing.
214 				prepareRefresh();
215 
216 				// Tell the subclass to refresh the internal bean factory.
217 				beanFactory = obtainFreshBeanFactory();
218 
219 				// Prepare the bean factory for use in this context.
220 				prepareBeanFactory(beanFactory);
221 
222 				try {
223 					// Allows post-processing of the bean factory in context
224 					// subclasses.
225 					postProcessBeanFactory(beanFactory);
226 
227 					// Invoke factory processors registered as beans in the
228 					// context.
229 					invokeBeanFactoryPostProcessors(beanFactory);
230 
231 					// Register bean processors that intercept bean creation.
232 					registerBeanPostProcessors(beanFactory);
233 
234 				}
235 				catch (BeansException ex) {
236 					// Destroy already created singletons to avoid dangling
237 					// resources.
238 					beanFactory.destroySingletons();
239 					cancelRefresh(ex);
240 					// propagate exception to the caller
241 					throw ex;
242 				}
243 			}
244 		}
245 		catch (Throwable th) {
246 			logger.error("Pre refresh error", th);
247 			// send failure event
248 			sendFailedEvent(th);
249 			// rethrow the problem w/o rewrapping
250 			if (th instanceof RuntimeException) {
251 				throw (RuntimeException) th;
252 			}
253 			else {
254 				throw (Error) th;
255 			}
256 		}
257 		finally {
258 			thread.setContextClassLoader(oldTCCL);
259 		}
260 	}
261 
262 	public void completeRefresh() {
263 		Thread thread = Thread.currentThread();
264 		ClassLoader oldTCCL = thread.getContextClassLoader();
265 
266 		try {
267 
268 			synchronized (contextMonitor) {
269 				thread.setContextClassLoader(contextClassLoaderProvider().getContextClassLoader());
270 
271 				try {
272 					ConfigurableListableBeanFactory beanFactory = getBeanFactory();
273 
274 					// Initialize message source for this context.
275 					initMessageSource();
276 
277 					// Initialize event multicaster for this context.
278 					initApplicationEventMulticaster();
279 
280 					// Initialize other special beans in specific context
281 					// subclasses.
282 					onRefresh();
283 
284 					// Check for listener beans and register them.
285 					registerListeners();
286 
287 					// Instantiate all remaining (non-lazy-init) singletons.
288 					finishBeanFactoryInitialization(beanFactory);
289 
290 					// Last step: publish corresponding event.
291 					finishRefresh();
292 
293 					// everything went okay, post notification
294 					sendRefreshedEvent();
295 				}
296 				catch (BeansException ex) {
297 					// Destroy already created singletons to avoid dangling
298 					// resources.
299 					getBeanFactory().destroySingletons();
300 					cancelRefresh(ex);
301 					// propagate exception to the caller
302 					throw ex;
303 				}
304 			}
305 		}
306 		catch (Throwable th) {
307 			logger.error("Post refresh error", th);
308 			// post notification
309 			sendFailedEvent(th);
310 			// rethrow the problem w/o rewrapping
311 			if (th instanceof RuntimeException) {
312 				throw (RuntimeException) th;
313 			}
314 			else {
315 				throw (Error) th;
316 			}
317 		}
318 		finally {
319 			thread.setContextClassLoader(oldTCCL);
320 		}
321 	}
322 
323 	protected void finishRefresh() {
324 		super.finishRefresh();
325 
326 		synchronized (availableMonitor) {
327 			available = true;
328 		}
329 		// publish the context only after all the beans have been published
330 		publishContextAsOsgiServiceIfNecessary();
331 	}
332 
333 	public Object getMonitor() {
334 		return contextMonitor;
335 	}
336 
337 	public void setExecutor(OsgiBundleApplicationContextExecutor executor) {
338 		this.executor = executor;
339 	}
340 
341 	public void setDelegatedEventMulticaster(OsgiBundleApplicationContextEventMulticaster multicaster) {
342 		this.delegatedMulticaster = multicaster;
343 	}
344 
345 	/**
346 	 * Sets the OSGi multicaster by using a Spring
347 	 * {@link ApplicationEventMulticaster}. This method is added as a
348 	 * covenience.
349 	 * 
350 	 * @param multicaster Spring multi-caster used for propagating OSGi specific
351 	 * events
352 	 * 
353 	 * @see OsgiBundleApplicationContextEventMulticasterAdapter
354 	 */
355 	public void setDelegatedEventMulticaster(ApplicationEventMulticaster multicaster) {
356 		this.delegatedMulticaster = new OsgiBundleApplicationContextEventMulticasterAdapter(multicaster);
357 	}
358 
359 	public OsgiBundleApplicationContextEventMulticaster getDelegatedEventMulticaster() {
360 		return this.delegatedMulticaster;
361 	}
362 
363 	private void sendFailedEvent(Throwable cause) {
364 		if (delegatedMulticaster != null)
365 			delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(this, this.getBundle(), cause));
366 	}
367 
368 	private void sendRefreshedEvent() {
369 		if (delegatedMulticaster != null)
370 			delegatedMulticaster.multicastEvent(new OsgiBundleContextRefreshedEvent(this, this.getBundle()));
371 	}
372 
373 	/**
374 	 * Returns the context class loader to be used as the Thread Context Class
375 	 * Loader for {@link #refresh()} and {@link #destroy()} calls.
376 	 * 
377 	 * The default implementation returns the bean class loader if it is set or
378 	 * or the current context class loader otherwise.
379 	 * 
380 	 * @return the thread context class loader to be used during the execution
381 	 * of critical section blocks
382 	 * @deprecated will be removed after RC1 is released
383 	 */
384 	protected ClassLoader getContextClassLoader() {
385 		return contextClassLoaderProvider().getContextClassLoader();
386 	}
387 
388 	/** private method used for doing lazy-init-if-not-set for cclProvider */
389 	private ContextClassLoaderProvider contextClassLoaderProvider() {
390 		if (cclProvider == null) {
391 			DefaultContextClassLoaderProvider defaultProvider = new DefaultContextClassLoaderProvider();
392 			defaultProvider.setBeanClassLoader(getClassLoader());
393 			cclProvider = defaultProvider;
394 		}
395 		return cclProvider;
396 	}
397 
398 	/**
399 	 * Sets the {@link ContextClassLoaderProvider} used by this OSGi application
400 	 * context instance. By default, {@link DefaultContextClassLoaderProvider}
401 	 * is used.
402 	 * 
403 	 * @param contextClassLoaderProvider context class loader provider to use
404 	 * @see ContextClassLoaderProvider
405 	 * @see DefaultContextClassLoaderProvider
406 	 */
407 	public void setContextClassLoaderProvider(ContextClassLoaderProvider contextClassLoaderProvider) {
408 		this.cclProvider = contextClassLoaderProvider;
409 	}
410 }