1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
140 state = ContextState.STARTED;
141 }
142
143
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
169 init();
170
171
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
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
234 waitBarrier.decrement();
235 }
236 };
237 }
238 else
239 task = new Runnable() {
240
241 public void run() {
242
243 stageTwo();
244 }
245 };
246
247 DependencyServiceManager dl = createDependencyServiceListener(task);
248 dl.findServiceDependencies();
249
250
251 if (dl.isSatisfied()) {
252 log.info("No outstanding OSGi service dependencies, completing initialization for " + getDisplayName());
253 stageTwo();
254 }
255
256 else {
257
258
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
270 if (waitBarrier.waitForZero(timeout)) {
271 timeout();
272 }
273 else
274 stageTwo();
275
276 }
277 else {
278
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
311
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
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
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
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
402 delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(delegateContext,
403 delegateContext.getBundle(), t));
404
405
406
407
408 }
409
410 /**
411 * Cancel waiting due to timeout.
412 */
413 private void timeout() {
414 synchronized (monitor) {
415
416
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
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 }