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  
18  package org.springframework.osgi.extender.internal.activator;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Timer;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.osgi.framework.Bundle;
30  import org.osgi.framework.BundleActivator;
31  import org.osgi.framework.BundleContext;
32  import org.osgi.framework.BundleEvent;
33  import org.osgi.framework.ServiceReference;
34  import org.osgi.framework.SynchronousBundleListener;
35  import org.osgi.framework.Version;
36  import org.osgi.service.packageadmin.PackageAdmin;
37  import org.springframework.beans.factory.DisposableBean;
38  import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
39  import org.springframework.core.CollectionFactory;
40  import org.springframework.core.task.SyncTaskExecutor;
41  import org.springframework.core.task.TaskExecutor;
42  import org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext;
43  import org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext;
44  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticaster;
45  import org.springframework.osgi.context.event.OsgiBundleApplicationContextListener;
46  import org.springframework.osgi.extender.OsgiApplicationContextCreator;
47  import org.springframework.osgi.extender.internal.dependencies.shutdown.ComparatorServiceDependencySorter;
48  import org.springframework.osgi.extender.internal.dependencies.shutdown.ServiceDependencySorter;
49  import org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor;
50  import org.springframework.osgi.extender.internal.support.ExtenderConfiguration;
51  import org.springframework.osgi.extender.internal.support.NamespaceManager;
52  import org.springframework.osgi.extender.internal.support.OsgiAnnotationPostProcessor;
53  import org.springframework.osgi.extender.internal.support.OsgiBeanFactoryPostProcessorAdapter;
54  import org.springframework.osgi.extender.internal.util.concurrent.Counter;
55  import org.springframework.osgi.extender.internal.util.concurrent.RunnableTimedExecution;
56  import org.springframework.osgi.extender.support.ApplicationContextConfiguration;
57  import org.springframework.osgi.extender.support.internal.ConfigUtils;
58  import org.springframework.osgi.service.importer.support.Cardinality;
59  import org.springframework.osgi.service.importer.support.CollectionType;
60  import org.springframework.osgi.service.importer.support.OsgiServiceCollectionProxyFactoryBean;
61  import org.springframework.osgi.util.OsgiBundleUtils;
62  import org.springframework.osgi.util.OsgiStringUtils;
63  import org.springframework.util.Assert;
64  
65  /**
66   * Osgi Extender that bootstraps 'Spring powered bundles'.
67   * 
68   * <p/> The class listens to bundle events and manages the creation and
69   * destruction of application contexts for bundles that have one or both of:
70   * <ul>
71   * <li> A manifest header entry Spring-Context
72   * <li> XML files in META-INF/spring folder
73   * </ul>
74   * 
75   * <p/> The extender also discovers any Spring namespace/schema handlers in
76   * resolved bundles and makes them available through a dedicated OSGi service.
77   * 
78   * <p/> The extender behaviour can be customized by attaching fragments to the
79   * extender bundle. On startup, the extender will look for
80   * <code>META-INF/spring/*.xml</code> files and merge them into an application
81   * context. From the resulting context, the context will look for beans with
82   * predefined names to determine its configuration. The current version
83   * recognises the following bean names:
84   * 
85   * <table border="1">
86   * <tr>
87   * <th>Bean Name</th>
88   * <th>Bean Type</th>
89   * <th>Description</th>
90   * </tr>
91   * <tr>
92   * <td><code>taskExecutor</code></td>
93   * <td><code>org.springframework.core.task.TaskExecutor</code></td>
94   * <td>Task executor used for creating the discovered application contexts.</td>
95   * </tr>
96   * <tr>
97   * <td><code>shutdownTaskExecutor</code></td>
98   * <td><code>org.springframework.core.task.TaskExecutor</code></td>
99   * <td>Task executor used for shutting down various application contexts.</td>
100  * </tr>
101  * <tr>
102  * <td><code>extenderProperties</code></td>
103  * <td><code>java.util.Properties</code></td>
104  * <td>Various properties for configuring the extender behaviour (see below)</td>
105  * </tr>
106  * </table>
107  * 
108  * <p/> <code>extenderProperties</code> recognises the following properties:
109  * 
110  * <table border="1">
111  * <tr>
112  * <th>Name</th>
113  * <th>Type</th>
114  * <th>Description</th>
115  * </tr>
116  * <tr>
117  * <td><code>shutdown.wait.time</code></td>
118  * <td>Number</td>
119  * <td>The amount of time the extender will wait for each application context
120  * to shutdown gracefully. Expressed in milliseconds.</td>
121  * </tr>
122  * <tr>
123  * <td><code>process.annotations</code></td>
124  * <td>Boolean</td>
125  * <td>Whether or not, the extender will process SpringOSGi annotations.</td>
126  * </tr>
127  * </table>
128  * 
129  * <p/> Note: The extender configuration context is created during the bundle
130  * activation (a synchronous OSGi lifecycle callback) and should contain only
131  * simple bean definitions that will not delay context initialisation.
132  * </p>
133  * 
134  * @author Bill Gallagher
135  * @author Andy Piper
136  * @author Hal Hildebrand
137  * @author Adrian Colyer
138  * @author Costin Leau
139  */
140 public class ContextLoaderListener implements BundleActivator {
141 
142 	/**
143 	 * Common base class for {@link ContextLoaderListener} listeners.
144 	 * 
145 	 * @author Costin Leau
146 	 */
147 	private abstract class BaseListener implements SynchronousBundleListener {
148 
149 		/**
150 		 * A bundle has been started, stopped, resolved, or unresolved. This
151 		 * method is a synchronous callback, do not do any long-running work in
152 		 * this thread.
153 		 * 
154 		 * @see org.osgi.framework.SynchronousBundleListener#bundleChanged
155 		 */
156 		public void bundleChanged(BundleEvent event) {
157 
158 			boolean trace = log.isTraceEnabled();
159 
160 			// check if the listener is still alive
161 			synchronized (monitor) {
162 				if (isClosed) {
163 					if (trace)
164 						log.trace("Listener is closed; events are being ignored");
165 					return;
166 				}
167 			}
168 			if (trace) {
169 				log.debug("Processing bundle event [" + OsgiStringUtils.nullSafeToString(event) + "] for bundle ["
170 						+ OsgiStringUtils.nullSafeSymbolicName(event.getBundle()) + "]");
171 			}
172 			try {
173 				handleEvent(event);
174 			}
175 			catch (Exception ex) {
176 				/* log exceptions before swallowing */
177 				log.warn("Got exception while handling event " + event, ex);
178 			}
179 		}
180 
181 		protected abstract void handleEvent(BundleEvent event);
182 	}
183 
184 	/**
185 	 * Bundle listener used for detecting namespace handler/resolvers. Exists as
186 	 * a separate listener so that it can be registered early to avoid race
187 	 * conditions with bundles in INSTALLING state but still to avoid premature
188 	 * context creation before the Spring {@link ContextLoaderListener} is not
189 	 * fully initialized.
190 	 * 
191 	 * @author Costin Leau
192 	 */
193 	private class NamespaceBundleLister extends BaseListener {
194 
195 		protected void handleEvent(BundleEvent event) {
196 
197 			Bundle bundle = event.getBundle();
198 
199 			switch (event.getType()) {
200 				case BundleEvent.RESOLVED: {
201 					maybeAddNamespaceHandlerFor(bundle);
202 					break;
203 				}
204 				case BundleEvent.UNRESOLVED: {
205 					maybeRemoveNameSpaceHandlerFor(bundle);
206 					break;
207 				}
208 				default:
209 					break;
210 			}
211 		}
212 	}
213 
214 	/**
215 	 * Bundle listener used for context creation/destruction.
216 	 */
217 	private class ContextBundleListener extends BaseListener {
218 
219 		protected void handleEvent(BundleEvent event) {
220 
221 			Bundle bundle = event.getBundle();
222 
223 			// ignore current bundle for context creation
224 			if (bundle.getBundleId() == bundleId) {
225 				return;
226 			}
227 
228 			switch (event.getType()) {
229 				case BundleEvent.STARTED: {
230 					maybeCreateApplicationContextFor(bundle);
231 					break;
232 				}
233 				case BundleEvent.STOPPING: {
234 					if (OsgiBundleUtils.isSystemBundle(bundle)) {
235 						if (log.isDebugEnabled()) {
236 							log.debug("System bundle stopping");
237 						}
238 						// System bundle is shutting down; Special handling for
239 						// framework shutdown
240 						shutdown();
241 					}
242 					else {
243 						maybeCloseApplicationContextFor(bundle);
244 					}
245 					break;
246 				}
247 				default:
248 					break;
249 			}
250 		}
251 	}
252 
253 
254 	private static final Log log = LogFactory.getLog(ContextLoaderListener.class);
255 
256 	/** annotation processing system property */
257 	private static final String AUTO_ANNOTATION_PROCESSING = "org.springframework.osgi.extender.annotation.auto.processing";
258 
259 	// "Spring Application Context Creation Timer"
260 	private Timer timer = new Timer(true);
261 
262 	/** extender bundle id */
263 	private long bundleId;
264 
265 	/** extender configuration */
266 	private ExtenderConfiguration extenderConfiguration;
267 
268 	/**
269 	 * The contexts we are currently managing. Keys are bundle ids, values are
270 	 * ServiceDependentOsgiApplicationContexts for the application context
271 	 */
272 	private final Map managedContexts;
273 
274 	/** Task executor used for bootstraping the Spring contexts in async mode */
275 	private TaskExecutor taskExecutor;
276 
277 	/** ApplicationContext Creator */
278 	private OsgiApplicationContextCreator contextCreator;
279 
280 	/** BFPP list */
281 	private List postProcessors;
282 
283 	/**
284 	 * Task executor which uses the same thread for running tasks. Used when
285 	 * doing a synchronous wait-for-dependencies.
286 	 */
287 	private TaskExecutor sameThreadTaskExecutor = new SyncTaskExecutor();
288 
289 	/** listener counter - used to properly synchronize shutdown */
290 	private Counter contextsStarted = new Counter("contextsStarted");
291 
292 	/** Spring namespace/resolver manager */
293 	private NamespaceManager nsManager;
294 
295 	/** The bundle's context */
296 	private BundleContext bundleContext;
297 
298 	/** Bundle listener interested in context creation */
299 	private SynchronousBundleListener contextListener;
300 
301 	/** Bundle listener interested in namespace resolvers/parsers discovery */
302 	private SynchronousBundleListener nsListener;
303 
304 	/** Service-based dependency sorter for shutdown */
305 	private ServiceDependencySorter shutdownDependencySorter = new ComparatorServiceDependencySorter();
306 
307 	/**
308 	 * Monitor used for dealing with the bundle activator and synchronous bundle
309 	 * threads
310 	 */
311 	private transient final Object monitor = new Object();
312 
313 	/**
314 	 * flag indicating whether the context is down or not - useful during
315 	 * shutdown
316 	 */
317 	private boolean isClosed = false;
318 
319 	/** This extender version */
320 	private Version extenderVersion;
321 
322 	private OsgiBundleApplicationContextEventMulticaster multicaster;
323 
324 	/** listeners interested in monitoring managed OSGi appCtxs */
325 	private List applicationListeners;
326 
327 	/** dynamicList clean up hook */
328 	private DisposableBean applicationListenersCleaner;
329 	/** Spring compatibility checker */
330 	private SpringTypeCompatibilityChecker compatibilityChecker;
331 	/** Spring version used */
332 	private Bundle wiredSpringBundle;
333 	/** shutdown task executor */
334 	private TaskExecutor shutdownTaskExecutor;
335 
336 
337 	/** Required by the BundleActivator contract */
338 	public ContextLoaderListener() {
339 		this.managedContexts = CollectionFactory.createConcurrentMap(16);
340 	}
341 
342 	/**
343 	 * <p/> Called by OSGi when this bundle is started. Finds all previously
344 	 * resolved bundles and adds namespace handlers for them if necessary.
345 	 * </p>
346 	 * <p/> Creates application contexts for bundles started before the extender
347 	 * was started.
348 	 * </p>
349 	 * <p/> Registers a namespace/entity resolving service for use by web app
350 	 * contexts.
351 	 * </p>
352 	 * 
353 	 * @see org.osgi.framework.BundleActivator#start
354 	 */
355 	public void start(BundleContext context) throws Exception {
356 
357 		this.bundleContext = context;
358 		this.bundleId = context.getBundle().getBundleId();
359 
360 		this.extenderVersion = OsgiBundleUtils.getBundleVersion(context.getBundle());
361 		log.info("Starting [" + bundleContext.getBundle().getSymbolicName() + "] bundle v.[" + extenderVersion + "]");
362 
363 		detectSpringVersion(context);
364 
365 		compatibilityChecker = new SpringTypeCompatibilityChecker(bundleContext);
366 
367 		// Step 1 : discover existing namespaces (in case there are fragments with custom XML definitions)
368 		nsManager = new NamespaceManager(context);
369 
370 		// register listener first to make sure any bundles in INSTALLED state
371 		// are not lost
372 		nsListener = new NamespaceBundleLister();
373 		context.addBundleListener(nsListener);
374 
375 		Bundle[] previousBundles = context.getBundles();
376 
377 		for (int i = 0; i < previousBundles.length; i++) {
378 			Bundle bundle = previousBundles[i];
379 			if (OsgiBundleUtils.isBundleResolved(bundle)) {
380 				maybeAddNamespaceHandlerFor(bundle);
381 			}
382 		}
383 
384 		// discovery finished, publish the resolvers/parsers in the OSGi space
385 		nsManager.afterPropertiesSet();
386 
387 		// Step 2: initialize the extender configuration
388 		extenderConfiguration = new ExtenderConfiguration(context);
389 
390 		// initialize the configuration once namespace handlers have been detected
391 		this.taskExecutor = extenderConfiguration.getTaskExecutor();
392 		this.shutdownTaskExecutor = extenderConfiguration.getShutdownTaskExecutor();
393 
394 		this.contextCreator = extenderConfiguration.getContextCreator();
395 		this.postProcessors = extenderConfiguration.getPostProcessors();
396 		addDefaultPostProcessors(postProcessors);
397 
398 		// init the OSGi event dispatch/listening system
399 		initListenerService();
400 
401 		// Step 3: discover the bundles that are started
402 		// and require context creation
403 
404 		// register the context creation listener
405 		contextListener = new ContextBundleListener();
406 		// listen to any changes in bundles
407 		context.addBundleListener(contextListener);
408 		// get the bundles again to get an updated view
409 		previousBundles = context.getBundles();
410 
411 		// Instantiate all previously resolved bundles which are Spring
412 		// powered
413 		for (int i = 0; i < previousBundles.length; i++) {
414 			if (OsgiBundleUtils.isBundleActive(previousBundles[i])) {
415 				try {
416 					maybeCreateApplicationContextFor(previousBundles[i]);
417 				}
418 				catch (Throwable e) {
419 					log.warn("Cannot start bundle " + OsgiStringUtils.nullSafeSymbolicName(previousBundles[i])
420 							+ " due to", e);
421 				}
422 			}
423 		}
424 
425 	}
426 
427 	private void detectSpringVersion(BundleContext context) {
428 		boolean debug = log.isDebugEnabled();
429 
430 		// use PackageAdmin internally to determine the wired Spring version
431 		ServiceReference ref = bundleContext.getServiceReference(PackageAdmin.class.getName());
432 		if (ref != null) {
433 			PackageAdmin pa = (PackageAdmin) bundleContext.getService(ref);
434 			wiredSpringBundle = pa.getBundle(Assert.class);
435 		}
436 		else {
437 			if (debug) {
438 				log.debug("PackageAdmin not available; falling back to raw class loading for detecting the wired Spring bundle");
439 			}
440 			wiredSpringBundle = SpringTypeCompatibilityChecker.findOriginatingBundle(context, Assert.class);
441 			if (wiredSpringBundle == null) {
442 				throw new IllegalStateException("Impossible to find the originating Spring bundle for " + Assert.class
443 						+ "; bailing out");
444 			}
445 		}
446 
447 		if (debug)
448 			log.debug("Spring-DM v.[" + extenderVersion + "] is wired to Spring core bundle "
449 					+ OsgiStringUtils.nullSafeSymbolicName(wiredSpringBundle) + " version ["
450 					+ OsgiBundleUtils.getBundleVersion(wiredSpringBundle) + "]");
451 	}
452 
453 	/**
454 	 * Called by OSGi when this bundled is stopped. Unregister the
455 	 * namespace/entity resolving service and clear all state. No further
456 	 * management of application contexts created by this extender prior to
457 	 * stopping the bundle occurs after this point (even if the extender bundle
458 	 * is subsequently restarted).
459 	 * 
460 	 * @see org.osgi.framework.BundleActivator#stop
461 	 */
462 	public void stop(BundleContext context) throws Exception {
463 		shutdown();
464 	}
465 
466 	/**
467 	 * Shutdown the extender and all bundled managed by it. Shutdown of contexts
468 	 * is in the topological order of the dependency graph formed by the service
469 	 * references.
470 	 */
471 	protected void shutdown() {
472 		synchronized (monitor) {
473 			// if already closed, bail out
474 			if (isClosed)
475 				return;
476 			else
477 				isClosed = true;
478 		}
479 		log.info("Stopping [" + bundleContext.getBundle().getSymbolicName() + "] bundle v.[" + extenderVersion + "]");
480 
481 		// first stop the watchdog
482 		stopTimer();
483 
484 		// remove the bundle listeners (we are closing down)
485 		if (contextListener != null) {
486 			bundleContext.removeBundleListener(contextListener);
487 			contextListener = null;
488 		}
489 
490 		if (nsListener != null) {
491 			bundleContext.removeBundleListener(nsListener);
492 			nsListener = null;
493 		}
494 
495 		// destroy bundles
496 		Bundle[] bundles = new Bundle[managedContexts.size()];
497 
498 		int i = 0;
499 		for (Iterator it = managedContexts.values().iterator(); it.hasNext();) {
500 			ConfigurableOsgiBundleApplicationContext context = (ConfigurableOsgiBundleApplicationContext) it.next();
501 			bundles[i++] = context.getBundle();
502 		}
503 
504 		bundles = shutdownDependencySorter.computeServiceDependencyGraph(bundles);
505 
506 		boolean debug = log.isDebugEnabled();
507 
508 		StringBuffer buffer = new StringBuffer();
509 		if (debug) {
510 			buffer.append("Shutdown order is: {");
511 			for (i = 0; i < bundles.length; i++) {
512 				buffer.append("\nBundle [" + bundles[i].getSymbolicName() + "]");
513 			}
514 			buffer.append("\n}");
515 			log.debug(buffer);
516 		}
517 
518 		final List taskList = new ArrayList(managedContexts.size());
519 		final List closedContexts = Collections.synchronizedList(new ArrayList());
520 		final Object[] contextClosingDown = new Object[1];
521 
522 		for (i = 0; i < bundles.length; i++) {
523 			Long id = new Long(bundles[i].getBundleId());
524 			final ConfigurableOsgiBundleApplicationContext context = (ConfigurableOsgiBundleApplicationContext) managedContexts.get(id);
525 			if (context != null) {
526 				closedContexts.add(context);
527 				// add a new runnable
528 				taskList.add(new Runnable() {
529 
530 					private final String toString = "Closing runnable for context " + context.getDisplayName();
531 
532 
533 					public void run() {
534 						contextClosingDown[0] = context;
535 						// eliminate context
536 						closedContexts.remove(context);
537 						if (log.isDebugEnabled())
538 							log.debug("Closing appCtx " + context.getDisplayName());
539 						context.close();
540 					}
541 
542 					public String toString() {
543 						return toString;
544 					}
545 				});
546 			}
547 		}
548 
549 		// tasks
550 		final Runnable[] tasks = (Runnable[]) taskList.toArray(new Runnable[taskList.size()]);
551 
552 		// start the ripper >:)
553 		for (int j = 0; j < tasks.length; j++) {
554 			if (RunnableTimedExecution.execute(tasks[j], extenderConfiguration.getShutdownWaitTime(),
555 				shutdownTaskExecutor)) {
556 				if (debug) {
557 					log.debug(contextClosingDown[0] + " context did not close successfully; forcing shutdown...");
558 				}
559 			}
560 		}
561 
562 		this.managedContexts.clear();
563 		// clear the namespace registry
564 		nsManager.destroy();
565 
566 		// release listeners
567 		if (applicationListeners != null) {
568 			applicationListeners = null;
569 			try {
570 				applicationListenersCleaner.destroy();
571 			}
572 			catch (Exception ex) {
573 				log.warn("exception thrown while releasing OSGi event listeners", ex);
574 			}
575 		}
576 
577 		// release multicaster
578 		if (multicaster != null) {
579 			multicaster.removeAllListeners();
580 			multicaster = null;
581 		}
582 
583 		// before bailing out; wait for the threads that might be left by
584 		// the task executor
585 		stopTaskExecutor();
586 
587 		extenderConfiguration.destroy();
588 	}
589 
590 	/**
591 	 * Cancel any tasks scheduled for the timer.
592 	 */
593 	private void stopTimer() {
594 		if (timer != null) {
595 			if (log.isDebugEnabled())
596 				log.debug("Canceling timer tasks");
597 			timer.cancel();
598 		}
599 		timer = null;
600 	}
601 
602 	/**
603 	 * Do some additional waiting so the service dependency listeners detect the
604 	 * shutdown.
605 	 */
606 	private void stopTaskExecutor() {
607 		boolean debug = log.isDebugEnabled();
608 
609 		if (debug)
610 			log.debug("Waiting for " + contextsStarted + " service dependency listener(s) to stop...");
611 
612 		contextsStarted.waitForZero(extenderConfiguration.getShutdownWaitTime());
613 
614 		if (!contextsStarted.isZero()) {
615 			if (debug)
616 				log.debug(contextsStarted.getValue()
617 						+ " service dependency listener(s) did not responded in time; forcing them to shutdown...");
618 			extenderConfiguration.setForceThreadShutdown(true);
619 		}
620 
621 		else
622 			log.debug("All listeners closed");
623 	}
624 
625 	/**
626 	 * Utility method that does extender range versioning and approapriate
627 	 * logging.
628 	 * 
629 	 * @param bundle
630 	 */
631 	private boolean handlerBundleMatchesExtenderVersion(Bundle bundle) {
632 		if (!ConfigUtils.matchExtenderVersionRange(bundle, extenderVersion)) {
633 			if (log.isDebugEnabled())
634 				log.debug("Bundle [" + OsgiStringUtils.nullSafeNameAndSymName(bundle)
635 						+ "] expects an extender w/ version["
636 						+ OsgiBundleUtils.getHeaderAsVersion(bundle, ConfigUtils.EXTENDER_VERSION)
637 						+ "] which does not match current extender w/ version[" + extenderVersion
638 						+ "]; skipping bundle from handler detection");
639 			return false;
640 		}
641 		return true;
642 	}
643 
644 	private void maybeAddNamespaceHandlerFor(Bundle bundle) {
645 		if (handlerBundleMatchesExtenderVersion(bundle))
646 			nsManager.maybeAddNamespaceHandlerFor(bundle);
647 	}
648 
649 	private void maybeRemoveNameSpaceHandlerFor(Bundle bundle) {
650 		if (handlerBundleMatchesExtenderVersion(bundle))
651 			nsManager.maybeRemoveNameSpaceHandlerFor(bundle);
652 	}
653 
654 	/**
655 	 * Context creation is a potentially long-running activity (certainly more
656 	 * than we want to do on the synchronous event callback).
657 	 * 
658 	 * <p/>Based on our configuration, the context can be started on the same
659 	 * thread or on a different one.
660 	 * 
661 	 * <p/> Kick off a background activity to create an application context for
662 	 * the given bundle if needed.
663 	 * 
664 	 * <b>Note:</b> Make sure to do the fastest filtering first to avoid
665 	 * slowdowns on platforms with a big number of plugins and wiring (i.e.
666 	 * Eclipse platform).
667 	 * 
668 	 * @param bundle
669 	 */
670 	protected void maybeCreateApplicationContextFor(Bundle bundle) {
671 
672 		boolean debug = log.isDebugEnabled();
673 		String bundleString = "[" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "]";
674 
675 		final Long bundleId = new Long(bundle.getBundleId());
676 
677 		if (managedContexts.containsKey(bundleId)) {
678 			if (debug) {
679 				log.debug("Bundle " + bundleString + " is already managed; ignoring...");
680 			}
681 			return;
682 		}
683 
684 		if (!ConfigUtils.matchExtenderVersionRange(bundle, extenderVersion)) {
685 			if (debug)
686 				log.debug("Bundle " + bundleString + " expects an extender w/ version["
687 						+ OsgiBundleUtils.getHeaderAsVersion(bundle, ConfigUtils.EXTENDER_VERSION)
688 						+ "] which does not match current extender w/ version[" + extenderVersion
689 						+ "]; skipping bundle from context creation");
690 			return;
691 		}
692 
693 		BundleContext localBundleContext = OsgiBundleUtils.getBundleContext(bundle);
694 
695 		if (debug)
696 			log.debug("Scanning bundle " + bundleString + " for configurations...");
697 
698 		// initialize context
699 		final DelegatedExecutionOsgiBundleApplicationContext localApplicationContext;
700 
701 		if (debug)
702 			log.debug("Creating an application context for bundle " + bundleString);
703 
704 		try {
705 			localApplicationContext = contextCreator.createApplicationContext(localBundleContext);
706 		}
707 		catch (Exception ex) {
708 			log.error("Cannot create application context for bundle " + bundleString, ex);
709 			return;
710 		}
711 
712 		if (localApplicationContext == null) {
713 			log.debug("No application context created for bundle " + bundleString);
714 			return;
715 		}
716 
717 		// an application context has been created - do type filtering
718 		// filtering could be applied before creating the application context but then user might disable this by accident
719 		// so its best to do this inside the extender itself (this could change in the future)
720 
721 		if (compatibilityChecker.checkCompatibility(bundle)) {
722 			log.debug("Bundle " + bundleString + " is Spring type compatible with Spring-DM");
723 
724 		}
725 		else {
726 			log.debug("Ignoring bundle " + bundleString + " as it's Spring incompatible with Spring-DM...");
727 			return;
728 		}
729 
730 		// create a dedicated hook for this application context
731 		BeanFactoryPostProcessor processingHook = new OsgiBeanFactoryPostProcessorAdapter(localBundleContext,
732 			postProcessors);
733 
734 		// add in the post processors
735 		localApplicationContext.addBeanFactoryPostProcessor(processingHook);
736 
737 		// add the context to the tracker
738 		managedContexts.put(bundleId, localApplicationContext);
739 
740 		localApplicationContext.setDelegatedEventMulticaster(multicaster);
741 
742 		// create refresh runnable
743 		Runnable contextRefresh = new Runnable() {
744 
745 			public void run() {
746 				localApplicationContext.refresh();
747 			}
748 		};
749 
750 		// executor used for creating the appCtx
751 		// chosen based on the sync/async configuration
752 		TaskExecutor executor = null;
753 
754 		ApplicationContextConfiguration config = new ApplicationContextConfiguration(bundle);
755 
756 		String creationType;
757 
758 		// synch/asynch context creation
759 		if (config.isCreateAsynchronously()) {
760 			// for the async stuff use the executor
761 			executor = taskExecutor;
762 			creationType = "Asynchronous";
763 		}
764 		else {
765 			// for the sync stuff, use this thread
766 			executor = sameThreadTaskExecutor;
767 			creationType = "Synchronous";
768 		}
769 
770 		if (debug) {
771 			log.debug(creationType + " context creation for bundle " + bundleString);
772 		}
773 
774 		// wait/no wait for dependencies behaviour
775 		if (config.isWaitForDependencies()) {
776 			DependencyWaiterApplicationContextExecutor appCtxExecutor = new DependencyWaiterApplicationContextExecutor(
777 				localApplicationContext, !config.isCreateAsynchronously(),
778 				extenderConfiguration.getDependencyFactories());
779 
780 			appCtxExecutor.setTimeout(config.getTimeout());
781 			appCtxExecutor.setWatchdog(timer);
782 			appCtxExecutor.setTaskExecutor(executor);
783 			appCtxExecutor.setMonitoringCounter(contextsStarted);
784 			// set events publisher
785 			appCtxExecutor.setDelegatedMulticaster(this.multicaster);
786 
787 			contextsStarted.increment();
788 		}
789 		else {
790 			// do nothing; by default contexts do not wait for services.
791 		}
792 
793 		executor.execute(contextRefresh);
794 	}
795 
796 	/**
797 	 * Closing an application context is a potentially long-running activity,
798 	 * however, we *have* to do it synchronously during the event process as the
799 	 * BundleContext object is not valid once we return from this method.
800 	 * 
801 	 * @param bundle
802 	 */
803 	protected void maybeCloseApplicationContextFor(Bundle bundle) {
804 		final ConfigurableOsgiBundleApplicationContext context = (ConfigurableOsgiBundleApplicationContext) managedContexts.remove(new Long(
805 			bundle.getBundleId()));
806 		if (context == null) {
807 			return;
808 		}
809 
810 		RunnableTimedExecution.execute(new Runnable() {
811 
812 			private final String toString = "Closing runnable for context " + context.getDisplayName();
813 
814 
815 			public void run() {
816 				if (context.isActive()) {
817 					context.close();
818 				}
819 			}
820 
821 			public String toString() {
822 				return toString;
823 			}
824 
825 		}, extenderConfiguration.getShutdownWaitTime(), shutdownTaskExecutor);
826 	}
827 
828 	private void initListenerService() {
829 		multicaster = extenderConfiguration.getEventMulticaster();
830 
831 		createListenersList();
832 		// register the listener that does the dispatching
833 		multicaster.addApplicationListener(new ListListenerAdapter(applicationListeners));
834 
835 		if (log.isDebugEnabled())
836 			log.debug("Initialization of OSGi listeners service completed...");
837 	}
838 
839 	/*
840 	 * Post process the context (for example by adding bean post processors).
841 	 */
842 	private void addDefaultPostProcessors(List postProcessorsList) {
843 		if (extenderConfiguration.shouldProcessAnnotation() || Boolean.getBoolean(AUTO_ANNOTATION_PROCESSING)) {
844 
845 			log.info("Enabled automatic Spring-DM annotation processing");
846 
847 			// add annotation BFPP (first in the list)
848 			if (log.isTraceEnabled())
849 				log.trace("Adding OSGi annotation post processor");
850 			postProcessorsList.add(0, new OsgiAnnotationPostProcessor());
851 		}
852 
853 		else {
854 			log.info("Disabled automatic Spring-DM annotation processing");
855 		}
856 	}
857 
858 	/**
859 	 * Creates a dynamic OSGi list of OSGi services interested in receiving
860 	 * events for OSGi application contexts.
861 	 */
862 	private void createListenersList() {
863 		OsgiServiceCollectionProxyFactoryBean fb = new OsgiServiceCollectionProxyFactoryBean();
864 		fb.setBundleContext(bundleContext);
865 		fb.setCardinality(Cardinality.C_0__N);
866 		fb.setCollectionType(CollectionType.LIST);
867 		fb.setInterfaces(new Class[] { OsgiBundleApplicationContextListener.class });
868 		fb.setBeanClassLoader(extenderConfiguration.getClassLoader());
869 		fb.afterPropertiesSet();
870 
871 		applicationListenersCleaner = fb;
872 		applicationListeners = (List) fb.getObject();
873 	}
874 }