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.extender.internal.dependencies.startup;
18  
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Timer;
22  import java.util.TimerTask;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.osgi.framework.Bundle;
27  import org.springframework.beans.BeansException;
28  import org.springframework.context.ApplicationContextException;
29  import org.springframework.context.ConfigurableApplicationContext;
30  import org.springframework.core.task.TaskExecutor;
31  import org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext;
32  import org.springframework.osgi.context.OsgiBundleApplicationContextExecutor;
33  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticaster;
34  import org.springframework.osgi.context.event.OsgiBundleContextFailedEvent;
35  import org.springframework.osgi.extender.internal.util.concurrent.Counter;
36  import org.springframework.osgi.util.OsgiStringUtils;
37  import org.springframework.util.Assert;
38  
39  /**
40   * Dependency waiter executor that breaks the 'traditional'
41   * {@link ConfigurableApplicationContext#refresh()} in two pieces so that beans
42   * are not actually created unless the OSGi service imported are present.
43   * 
44   * <p/>
45   * 
46   * Supports both asynch and synch behaviour.
47   * 
48   * @author Hal Hildebrand
49   * @author Costin Leau
50   */
51  public class DependencyWaiterApplicationContextExecutor implements OsgiBundleApplicationContextExecutor,
52  		ContextExecutorStateAccessor {
53  
54  	private static final Log log = LogFactory.getLog(DependencyWaiterApplicationContextExecutor.class);
55  
56  	/**
57  	 * this class monitor. Since multiple threads will access this object, we
58  	 * have to use synchronization to guarantee thread visibility
59  	 */
60  	private final Object monitor = new Object();
61  
62  	/** waiting timeout */
63  	private long timeout;
64  
65  	/** the timer used for executing the timeout */
66  	// NOTE: the dog is not managed by this application so do not cancel it
67  	private Timer watchdog;
68  
69  	/** watchdog task */
70  	private TimerTask watchdogTask;
71  
72  	/** OSGi service dependencyDetector used for detecting dependencies */
73  	protected DependencyServiceManager dependencyDetector;
74  
75  	protected final DelegatedExecutionOsgiBundleApplicationContext delegateContext;
76  
77  	/**
78  	 * State of the associated context from the executor POV.
79  	 */
80  	private ContextState state = ContextState.INITIALIZED;
81  
82  	private TaskExecutor taskExecutor;
83  
84  	/**
85  	 * A synchronized counter used by the Listener to determine the number of
86  	 * children to wait for when shutting down.
87  	 */
88  	private Counter monitorCounter;
89  
90  	/**
91  	 * Should the waiting hold the thread or not.
92  	 */
93  	private final boolean synchronousWait;
94  
95  	/**
96  	 * Counter used when waiting for dependencies to appear.
97  	 */
98  	private final Counter waitBarrier = new Counter("syncCounterWait");
99  
100 	/** delegated multicaster */
101 	private OsgiBundleApplicationContextEventMulticaster delegatedMulticaster;
102 
103 	private List dependencyFactories;
104 
105 
106 	/**
107 	 * The task for the watch dog.
108 	 * 
109 	 * @author Hal Hildebrand
110 	 */
111 	private class WatchDogTask extends TimerTask {
112 
113 		public void run() {
114 			timeout();
115 		}
116 	}
117 
118 	/**
119 	 * Create the Runnable action which will complete the context creation
120 	 * process. This process can be called synchronously or asynchronously,
121 	 * depending on context configuration and availability of dependencies.
122 	 * 
123 	 * @author Hal Hildebrand
124 	 * @author Costin Leau
125 	 * 
126 	 */
127 	private class CompleteRefreshTask implements Runnable {
128 
129 		public void run() {
130 			boolean debug = log.isDebugEnabled();
131 			if (debug)
132 				log.debug("Completing refresh for " + getDisplayName());
133 
134 			synchronized (monitor) {
135 				if (state != ContextState.DEPENDENCIES_RESOLVED) {
136 					logWrongState(ContextState.DEPENDENCIES_RESOLVED);
137 					return;
138 				}
139 				// otherwise update the state
140 				state = ContextState.STARTED;
141 			}
142 
143 			// Continue with the refresh process...
144 			synchronized (delegateContext.getMonitor()) {
145 				delegateContext.completeRefresh();
146 			}
147 		}
148 	}
149 
150 
151 	public DependencyWaiterApplicationContextExecutor(DelegatedExecutionOsgiBundleApplicationContext delegateContext,
152 			boolean syncWait, List dependencyFactories) {
153 		this.delegateContext = delegateContext;
154 		this.delegateContext.setExecutor(this);
155 		this.synchronousWait = syncWait;
156 		this.dependencyFactories = dependencyFactories;
157 	}
158 
159 	/**
160 	 * Provide a continuation like approach to the application context. Will
161 	 * execute just some parts of refresh and then leave the rest of to be
162 	 * executed after a number of conditions have been met.
163 	 */
164 	public void refresh() throws BeansException, IllegalStateException {
165 		if (log.isDebugEnabled())
166 			log.debug("Starting first stage of refresh for " + getDisplayName());
167 
168 		// sanity check
169 		init();
170 
171 		// start the first stage
172 		stageOne();
173 	}
174 
175 	/** Do some sanity checks */
176 	protected void init() {
177 		synchronized (monitor) {
178 			Assert.notNull(watchdog, "watchdog timer required");
179 			Assert.notNull(monitorCounter, " monitorCounter required");
180 			if (state != ContextState.INTERRUPTED && state != ContextState.STOPPED)
181 				state = ContextState.INITIALIZED;
182 			else {
183 				RuntimeException ex = new IllegalStateException("cannot refresh an interrupted/closed context");
184 				log.fatal(ex);
185 				throw ex;
186 			}
187 		}
188 	}
189 
190 	/**
191 	 * Start the first stage of the application context refresh. Determines the
192 	 * service dependencies and if there are any, registers a OSGi service
193 	 * dependencyDetector which will continue the refresh process
194 	 * asynchronously.
195 	 * 
196 	 * Based on the {@link #synchronousWait}, the current thread can simply end
197 	 * if there are any dependencies (the default) or wait to either timeout or
198 	 * have all its dependencies met.
199 	 * 
200 	 */
201 	protected void stageOne() {
202 
203 		boolean debug = log.isDebugEnabled();
204 
205 		try {
206 			if (debug)
207 				log.debug("Calling preRefresh on " + getDisplayName());
208 
209 			synchronized (monitor) {
210 
211 				// check before kicking the pedal
212 				if (state != ContextState.INITIALIZED) {
213 					logWrongState(ContextState.INITIALIZED);
214 					return;
215 				}
216 
217 				state = ContextState.RESOLVING_DEPENDENCIES;
218 			}
219 
220 			synchronized (delegateContext.getMonitor()) {
221 				delegateContext.startRefresh();
222 			}
223 
224 			if (debug)
225 				log.debug("Pre-refresh completed; determining dependencies...");
226 
227 			Runnable task = null;
228 
229 			if (synchronousWait) {
230 				task = new Runnable() {
231 
232 					public void run() {
233 						// inform the waiting thread through the counter
234 						waitBarrier.decrement();
235 					}
236 				};
237 			}
238 			else
239 				task = new Runnable() {
240 
241 					public void run() {
242 						// no waiting involved, just call stageTwo
243 						stageTwo();
244 					}
245 				};
246 
247 			DependencyServiceManager dl = createDependencyServiceListener(task);
248 			dl.findServiceDependencies();
249 
250 			// all dependencies are met, just go with stageTwo
251 			if (dl.isSatisfied()) {
252 				log.info("No outstanding OSGi service dependencies, completing initialization for " + getDisplayName());
253 				stageTwo();
254 			}
255 
256 			else {
257 				// there are dependencies not met
258 				// register a listener to look for them
259 				dependencyDetector = dl;
260 				if (debug)
261 					log.debug("Registering service dependency dependencyDetector for " + getDisplayName());
262 				dependencyDetector.register();
263 
264 				if (synchronousWait) {
265 					waitBarrier.increment();
266 					if (debug)
267 						log.debug("Synchronous wait-for-dependencies; waiting...");
268 
269 					// if waiting times out...
270 					if (waitBarrier.waitForZero(timeout)) {
271 						timeout();
272 					}
273 					else
274 						stageTwo();
275 
276 				}
277 				else {
278 					// start the watchdog (we're asynch)
279 					startWatchDog();
280 
281 					if (debug)
282 						log.debug("Asynch wait-for-dependencies; ending method");
283 				}
284 
285 			}
286 		}
287 		catch (Throwable e) {
288 			fail(e);
289 		}
290 
291 	}
292 
293 	protected void stageTwo() {
294 		boolean debug = log.isDebugEnabled();
295 
296 		if (debug)
297 			log.debug("Starting stage two for " + getDisplayName());
298 
299 		synchronized (monitor) {
300 
301 			if (state != ContextState.RESOLVING_DEPENDENCIES) {
302 				logWrongState(ContextState.RESOLVING_DEPENDENCIES);
303 				return;
304 			}
305 
306 			stopWatchDog();
307 			state = ContextState.DEPENDENCIES_RESOLVED;
308 		}
309 
310 		// always delegate to the taskExecutor since we might be called by the
311 		// OSGi platform listener
312 		taskExecutor.execute(new CompleteRefreshTask());
313 	}
314 
315 	/**
316 	 * The application context is being shutdown. Deregister the listener and
317 	 * prevent classes from being loaded since it's Doom's day.
318 	 */
319 	public void close() {
320 		boolean debug = log.isDebugEnabled();
321 
322 		boolean normalShutdown = false;
323 
324 		synchronized (monitor) {
325 
326 			// no need for cleanup
327 			if (state.isDown()) {
328 				return;
329 			}
330 
331 			if (debug)
332 				log.debug("Closing appCtx for " + getDisplayName());
333 
334 			if (dependencyDetector != null) {
335 				dependencyDetector.deregister();
336 			}
337 
338 			if (state == ContextState.STARTED) {
339 				if (debug)
340 					log.debug("Shutting down normaly appCtx " + getDisplayName());
341 				// close the context only if it was actually started
342 				state = ContextState.STOPPED;
343 				normalShutdown = true;
344 			}
345 			else {
346 				if (debug)
347 					log.debug("No need to stop context (it hasn't been started yet)");
348 				state = ContextState.INTERRUPTED;
349 			}
350 		}
351 		try {
352 			if (normalShutdown) {
353 				synchronized (delegateContext.getMonitor()) {
354 					delegateContext.normalClose();
355 				}
356 			}
357 		}
358 		catch (Exception ex) {
359 			log.fatal("Could not succesfully close context " + delegateContext, ex);
360 		}
361 		finally {
362 			monitorCounter.decrement();
363 		}
364 
365 	}
366 
367 	/**
368 	 * Fail creating the context. Figure out unsatisfied dependencies and
369 	 * provide a very nice log message before closing the appContext.
370 	 * 
371 	 * Normally this method is called when an exception is caught.
372 	 * 
373 	 * @param t - the offending Throwable which caused our demise
374 	 */
375 	private void fail(Throwable t) {
376 
377 		// this will not thrown any exceptions (it just logs them)
378 		close();
379 
380 		StringBuffer buf = new StringBuffer();
381 		if (dependencyDetector == null || dependencyDetector.getUnsatisfiedDependencies().isEmpty()) {
382 			buf.append("none");
383 		}
384 		else {
385 			for (Iterator dependencies = dependencyDetector.getUnsatisfiedDependencies().keySet().iterator(); dependencies.hasNext();) {
386 				MandatoryServiceDependency dependency = (MandatoryServiceDependency) dependencies.next();
387 				buf.append(dependency.toString());
388 				if (dependencies.hasNext()) {
389 					buf.append(", ");
390 				}
391 			}
392 		}
393 		StringBuffer message = new StringBuffer();
394 		message.append("Unable to create application context for [");
395 		message.append(getBundleSymbolicName());
396 		message.append("], unsatisfied dependencies: ");
397 		message.append(buf.toString());
398 
399 		log.error(message.toString(), t);
400 
401 		// send notification
402 		delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(delegateContext,
403 			delegateContext.getBundle(), t));
404 
405 		// rethrow the exception wrapped to the caller (and prevent bundles
406 		// started in sync mode to complete).
407 		// throw new ApplicationContextException("cannot refresh context", t);
408 	}
409 
410 	/**
411 	 * Cancel waiting due to timeout.
412 	 */
413 	private void timeout() {
414 		synchronized (monitor) {
415 			// deregister listener to get an accurate snapshot of the
416 			// unsatisfied dependencies.
417 
418 			if (dependencyDetector != null) {
419 				dependencyDetector.deregister();
420 			}
421 
422 			log.warn("Timeout occured before finding service dependencies for [" + delegateContext.getDisplayName()
423 					+ "]");
424 
425 			ApplicationContextException e = new ApplicationContextException("Application context initializition for '"
426 					+ OsgiStringUtils.nullSafeSymbolicName(getBundle()) + "' has timed out");
427 			e.fillInStackTrace();
428 			fail(e);
429 
430 		}
431 	}
432 
433 	protected DependencyServiceManager createDependencyServiceListener(Runnable task) {
434 		return new DependencyServiceManager(this, delegateContext, dependencyFactories, task, timeout);
435 	}
436 
437 	/**
438 	 * Schedule the watchdog task.
439 	 */
440 	protected void startWatchDog() {
441 		synchronized (monitor) {
442 			watchdogTask = new WatchDogTask();
443 			watchdog.schedule(watchdogTask, timeout);
444 		}
445 	}
446 
447 	protected void stopWatchDog() {
448 		synchronized (monitor) {
449 			if (watchdogTask != null) {
450 				watchdogTask.cancel();
451 				watchdogTask = null;
452 			}
453 		}
454 	}
455 
456 	/**
457 	 * Sets the timeout (in ms) for waiting for service dependencies.
458 	 * 
459 	 * @param timeout
460 	 */
461 	public void setTimeout(long timeout) {
462 		synchronized (monitor) {
463 			this.timeout = timeout;
464 		}
465 	}
466 
467 	public void setTaskExecutor(TaskExecutor taskExec) {
468 		synchronized (monitor) {
469 			this.taskExecutor = taskExec;
470 		}
471 	}
472 
473 	private Bundle getBundle() {
474 		synchronized (monitor) {
475 			return delegateContext.getBundle();
476 		}
477 	}
478 
479 	private String getDisplayName() {
480 		synchronized (monitor) {
481 			return delegateContext.getDisplayName();
482 		}
483 
484 	}
485 
486 	private String getBundleSymbolicName() {
487 		return OsgiStringUtils.nullSafeSymbolicName(getBundle());
488 	}
489 
490 	public void setWatchdog(Timer watchdog) {
491 		synchronized (monitor) {
492 			this.watchdog = watchdog;
493 		}
494 	}
495 
496 	/**
497 	 * Reduce the code pollution.
498 	 * 
499 	 * @param expected the expected value for the context state.
500 	 */
501 	private void logWrongState(ContextState expected) {
502 		log.error("Expecting state (" + expected + ") not (" + state + ") for context [" + getDisplayName()
503 				+ "]; assuming an interruption and bailing out");
504 	}
505 
506 	/**
507 	 * Pass in the context counter. Used by the listener to track the number of
508 	 * contexts started.
509 	 * 
510 	 * @param asynchCounter
511 	 */
512 	public void setMonitoringCounter(Counter contextsStarted) {
513 		this.monitorCounter = contextsStarted;
514 	}
515 
516 	/**
517 	 * Sets the multicaster for delegating failing events.
518 	 * 
519 	 * @param multicaster
520 	 */
521 	public void setDelegatedMulticaster(OsgiBundleApplicationContextEventMulticaster multicaster) {
522 		this.delegatedMulticaster = multicaster;
523 	}
524 
525 	//
526 	// accessor interface implementations
527 	//
528 
529 	public ContextState getContextState() {
530 		synchronized (monitor) {
531 			return state;
532 		}
533 	}
534 
535 	public OsgiBundleApplicationContextEventMulticaster getEventMulticaster() {
536 		return this.delegatedMulticaster;
537 	}
538 }