1 package com.atlassian.plugin.osgi.container.felix;
2
3 import com.atlassian.plugin.event.PluginEventListener;
4 import com.atlassian.plugin.event.PluginEventManager;
5 import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
6 import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
7 import com.atlassian.plugin.event.events.PluginUpgradedEvent;
8 import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartingEvent;
9 import com.atlassian.plugin.osgi.container.OsgiContainerException;
10 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
11 import com.atlassian.plugin.osgi.container.PackageScannerConfiguration;
12 import com.atlassian.plugin.osgi.container.OsgiPersistentCache;
13 import com.atlassian.plugin.osgi.container.impl.DefaultOsgiPersistentCache;
14 import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
15 import com.atlassian.plugin.osgi.hostcomponents.HostComponentRegistration;
16 import com.atlassian.plugin.osgi.hostcomponents.impl.DefaultComponentRegistrar;
17 import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
18 import com.atlassian.plugin.util.ClassLoaderUtils;
19
20 import org.apache.commons.io.FileUtils;
21 import org.apache.commons.lang.Validate;
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.apache.felix.framework.Felix;
25 import org.apache.felix.framework.Logger;
26 import org.apache.felix.framework.cache.BundleCache;
27 import org.apache.felix.framework.util.FelixConstants;
28 import org.apache.felix.framework.util.StringMap;
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.BundleException;
34 import org.osgi.framework.BundleListener;
35 import org.osgi.framework.Constants;
36 import org.osgi.framework.FrameworkEvent;
37 import org.osgi.framework.FrameworkListener;
38 import org.osgi.framework.ServiceReference;
39 import org.osgi.framework.ServiceRegistration;
40 import org.osgi.service.packageadmin.PackageAdmin;
41 import org.osgi.util.tracker.ServiceTracker;
42
43 import java.io.File;
44 import java.io.FilenameFilter;
45 import java.io.IOException;
46 import java.net.URL;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.concurrent.ThreadFactory;
53 import java.util.concurrent.CountDownLatch;
54 import java.util.concurrent.TimeUnit;
55 import java.util.jar.JarFile;
56
57
58
59
60 public class FelixOsgiContainerManager implements OsgiContainerManager
61 {
62 public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
63
64 private static final Log log = LogFactory.getLog(FelixOsgiContainerManager.class);
65 private static final String OSGI_BOOTDELEGATION = "org.osgi.framework.bootdelegation";
66 private static final String ATLASSIAN_PREFIX = "atlassian.";
67
68 private final OsgiPersistentCache persistentCache;
69 private final URL frameworkBundlesUrl;
70 private final PackageScannerConfiguration packageScannerConfig;
71 private final HostComponentProvider hostComponentProvider;
72 private final Set<ServiceTracker> trackers;
73 private final ExportsBuilder exportsBuilder;
74 private final ThreadFactory threadFactory = new ThreadFactory()
75 {
76 public Thread newThread(final Runnable r)
77 {
78 final Thread thread = new Thread(r, "Felix:Startup");
79 thread.setDaemon(true);
80 return thread;
81 }
82 };
83
84 private BundleRegistration registration = null;
85 private Felix felix = null;
86 private boolean felixRunning = false;
87 private boolean disableMultipleBundleVersions = true;
88 private Logger felixLogger;
89
90
91
92
93
94
95
96
97
98
99 @Deprecated
100 public FelixOsgiContainerManager(final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
101 {
102 this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir, packageScannerConfig,
103 provider, eventManager);
104 }
105
106
107
108
109
110
111
112
113
114
115
116 @Deprecated
117 public FelixOsgiContainerManager(final URL frameworkBundlesZip, final File frameworkBundlesDir, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
118 {
119 this(frameworkBundlesZip, new DefaultOsgiPersistentCache(new File(frameworkBundlesDir.getParentFile(),
120 "osgi-cache")), packageScannerConfig, provider, eventManager);
121 }
122
123
124
125
126
127
128
129
130
131
132 public FelixOsgiContainerManager(final OsgiPersistentCache persistentCache, final PackageScannerConfiguration packageScannerConfig, final HostComponentProvider provider, final PluginEventManager eventManager)
133 {
134 this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), persistentCache, packageScannerConfig,
135 provider, eventManager);
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public FelixOsgiContainerManager(final URL frameworkBundlesZip, OsgiPersistentCache persistentCache,
151 final PackageScannerConfiguration packageScannerConfig,
152 final HostComponentProvider provider, final PluginEventManager eventManager)
153 throws OsgiContainerException
154 {
155 Validate.notNull(frameworkBundlesZip, "The framework bundles zip is required");
156 Validate.notNull(persistentCache, "The framework bundles directory must not be null");
157 Validate.notNull(packageScannerConfig, "The package scanner configuration must not be null");
158 Validate.notNull(eventManager, "The plugin event manager is required");
159
160 frameworkBundlesUrl = frameworkBundlesZip;
161 this.packageScannerConfig = packageScannerConfig;
162 this.persistentCache = persistentCache;
163 hostComponentProvider = provider;
164 trackers = Collections.synchronizedSet(new HashSet<ServiceTracker>());
165 eventManager.register(this);
166 felixLogger = new FelixLoggerBridge(log);
167 exportsBuilder = new ExportsBuilder();
168 }
169
170 public void setFelixLogger(final Logger logger)
171 {
172 felixLogger = logger;
173 }
174
175 public void setDisableMultipleBundleVersions(final boolean val)
176 {
177 disableMultipleBundleVersions = val;
178 }
179
180 @PluginEventListener
181 public void onStart(final PluginFrameworkStartingEvent event)
182 {
183 start();
184 }
185
186 @PluginEventListener
187 public void onShutdown(final PluginFrameworkShutdownEvent event)
188 {
189 stop();
190 }
191
192 @PluginEventListener
193 public void onPluginUpgrade(PluginUpgradedEvent event)
194 {
195 registration.refreshPackages();
196 }
197
198 @PluginEventListener
199 public void onPluginFrameworkWarmRestarting(PluginFrameworkWarmRestartingEvent event)
200 {
201 registration.loadHostComponents(collectHostComponents(hostComponentProvider));
202 }
203
204 public void start() throws OsgiContainerException
205 {
206 if (isRunning())
207 {
208 return;
209 }
210
211 final DefaultComponentRegistrar registrar = collectHostComponents(hostComponentProvider);
212
213 final StringMap configMap = new StringMap(false);
214
215 configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
216
217
218 configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, exportsBuilder.getExports(registrar.getRegistry(), packageScannerConfig));
219
220
221 configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, persistentCache.getOsgiBundleCache().getAbsolutePath());
222
223 configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(felixLogger.getLogLevel()));
224 String bootDelegation = getAtlassianSpecificOsgiSystemProperty(OSGI_BOOTDELEGATION);
225 if ((bootDelegation == null) || (bootDelegation.trim().length() == 0))
226 {
227 bootDelegation = "weblogic.*,META-INF.services,com.yourkit.*,com.jprofiler.*,org.apache.xerces.*,com.icl.saxon";
228 }
229
230 configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
231 if (log.isDebugEnabled())
232 {
233 log.debug("Felix configuration: " + configMap);
234 }
235
236 validateConfiguration(configMap);
237
238 try
239 {
240
241 registration = new BundleRegistration(frameworkBundlesUrl, persistentCache.getFrameworkBundleCache(), registrar);
242 final List<BundleActivator> list = new ArrayList<BundleActivator>();
243 list.add(registration);
244
245
246
247 felix = new Felix(felixLogger, configMap, list);
248
249
250 final Runnable start = new Runnable()
251 {
252 public void run()
253 {
254 try
255 {
256 Thread.currentThread().setContextClassLoader(null);
257 felix.start();
258 felixRunning = true;
259 }
260 catch (final BundleException e)
261 {
262 throw new OsgiContainerException("Unable to start felix", e);
263 }
264 }
265 };
266 final Thread t = threadFactory.newThread(start);
267 t.start();
268
269
270 t.join(10 * 60 * 1000);
271 }
272 catch (final Exception ex)
273 {
274 throw new OsgiContainerException("Unable to start OSGi container", ex);
275 }
276 }
277
278
279
280
281
282
283 private void validateConfiguration(StringMap configMap) throws OsgiContainerException
284 {
285 String systemExports = (String) configMap.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
286 validateCaches(systemExports);
287 detectIncorrectOsgiVersion();
288 detectXercesOverride(systemExports);
289 }
290
291
292
293
294
295
296
297
298 void detectXercesOverride(String systemExports) throws OsgiContainerException
299 {
300 int pos = systemExports.indexOf("org.apache.xerces.util");
301 if (pos > -1)
302 {
303 pos += "org.apache.xerces.util".length();
304
305
306 if (pos >= systemExports.length() || ';' != systemExports.charAt(pos))
307 {
308 throw new OsgiContainerException(
309 "Detected an incompatible version of Apache Xerces on the classpath. If using Tomcat, you may have " +
310 "an old version of Xerces in $TOMCAT_HOME/common/lib/endorsed that will need to be removed.");
311 }
312 }
313 }
314
315
316
317
318
319
320
321 private void validateCaches(String systemExports)
322 {
323 String cacheKey = String.valueOf(systemExports.hashCode());
324 persistentCache.validate(cacheKey);
325
326 log.debug("Using Felix bundle cache directory :" + persistentCache.getOsgiBundleCache().getAbsolutePath());
327 }
328
329
330
331
332 private void detectIncorrectOsgiVersion()
333 {
334 try
335 {
336 Bundle.class.getMethod("getBundleContext");
337 }
338 catch (final NoSuchMethodException e)
339 {
340 throw new OsgiContainerException(
341 "Detected older version (4.0 or earlier) of OSGi. If using WebSphere " + "6.1, please enable application-first (parent-last) classloading and the 'Single classloader for " + "application' WAR classloader policy.");
342 }
343 }
344
345 public void stop() throws OsgiContainerException
346 {
347 if (felixRunning)
348 {
349 for (final ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
350 {
351 tracker.close();
352 }
353 felix.stopAndWait();
354 }
355
356 felixRunning = false;
357 felix = null;
358 }
359
360 public Bundle[] getBundles()
361 {
362 if (isRunning())
363 {
364 return registration.getBundles();
365 }
366 else
367 {
368 throw new IllegalStateException(
369 "Cannot retrieve the bundles if the Felix container isn't running. Check earlier in the logs for the possible cause as to why Felix didn't start correctly.");
370 }
371 }
372
373 public ServiceReference[] getRegisteredServices()
374 {
375 return felix.getRegisteredServices();
376 }
377
378 public ServiceTracker getServiceTracker(final String cls)
379 {
380 if (!isRunning())
381 {
382 throw new IllegalStateException("Unable to create a tracker when osgi is not running");
383 }
384
385 final ServiceTracker tracker = registration.getServiceTracker(cls, trackers);
386 tracker.open();
387 trackers.add(tracker);
388 return tracker;
389 }
390
391 public Bundle installBundle(final File file) throws OsgiContainerException
392 {
393 try
394 {
395 return registration.install(file, disableMultipleBundleVersions);
396 }
397 catch (final BundleException e)
398 {
399 throw new OsgiContainerException("Unable to install bundle", e);
400 }
401 }
402
403 DefaultComponentRegistrar collectHostComponents(final HostComponentProvider provider)
404 {
405 final DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
406 if (provider != null)
407 {
408 provider.provide(registrar);
409 }
410 return registrar;
411 }
412
413 public boolean isRunning()
414 {
415 return felixRunning;
416 }
417
418 public List<HostComponentRegistration> getHostComponentRegistrations()
419 {
420 return registration.getHostComponentRegistrations();
421 }
422
423 private String getAtlassianSpecificOsgiSystemProperty(final String originalSystemProperty)
424 {
425 return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
426 }
427
428
429
430
431
432 static class BundleRegistration implements BundleActivator, BundleListener, FrameworkListener
433 {
434 private BundleContext bundleContext;
435 private DefaultComponentRegistrar registrar;
436 private List<ServiceRegistration> hostServicesReferences;
437 private List<HostComponentRegistration> hostComponentRegistrations;
438 private final URL frameworkBundlesUrl;
439 private final File frameworkBundlesDir;
440 private final ClassLoader initializedClassLoader;
441 private PackageAdmin packageAdmin;
442
443 public BundleRegistration(final URL frameworkBundlesUrl, final File frameworkBundlesDir, final DefaultComponentRegistrar registrar)
444 {
445 this.registrar = registrar;
446 this.frameworkBundlesUrl = frameworkBundlesUrl;
447 this.frameworkBundlesDir = frameworkBundlesDir;
448 this.initializedClassLoader = Thread.currentThread().getContextClassLoader();
449 }
450
451 public void start(final BundleContext context) throws Exception
452 {
453 bundleContext = context;
454 final ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
455 packageAdmin = (PackageAdmin) context.getService(ref);
456
457 context.addBundleListener(this);
458 context.addFrameworkListener(this);
459
460 loadHostComponents(registrar);
461 extractAndInstallFrameworkBundles();
462 }
463
464 public void stop(final BundleContext ctx) throws Exception
465 {
466 ctx.removeBundleListener(this);
467 ctx.removeFrameworkListener(this);
468 if (hostServicesReferences != null)
469 {
470 for (ServiceRegistration ref : hostServicesReferences)
471 {
472 ref.unregister();
473 }
474 }
475 bundleContext = null;
476 packageAdmin = null;
477 hostServicesReferences = null;
478 hostComponentRegistrations = null;
479 registrar = null;
480 }
481
482 public void bundleChanged(final BundleEvent evt)
483 {
484 switch (evt.getType())
485 {
486 case BundleEvent.INSTALLED:
487 log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
488 break;
489 case BundleEvent.RESOLVED:
490 log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
491 break;
492 case BundleEvent.UNRESOLVED:
493 log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
494 break;
495 case BundleEvent.STARTED:
496 log.info("Started bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
497 break;
498 case BundleEvent.STOPPED:
499 log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
500 break;
501 case BundleEvent.UNINSTALLED:
502 log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " (" + evt.getBundle().getBundleId() + ")");
503 break;
504 }
505 }
506
507 public Bundle install(final File path, final boolean uninstallOtherVersions) throws BundleException
508 {
509 boolean bundleUninstalled = false;
510 if (uninstallOtherVersions)
511 {
512 try
513 {
514 JarFile jar = null;
515 String pluginKey = null;
516 try
517 {
518 jar = new JarFile(path);
519 pluginKey = OsgiHeaderUtil.getPluginKey(jar.getManifest());
520 }
521 finally
522 {
523 jar.close();
524 }
525 for (final Bundle oldBundle : bundleContext.getBundles())
526 {
527 if (pluginKey.equals(OsgiHeaderUtil.getPluginKey(oldBundle)))
528 {
529 log.info("Uninstalling existing version " + oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
530 oldBundle.uninstall();
531 bundleUninstalled = true;
532 }
533 }
534 }
535 catch (final IOException e)
536 {
537 throw new BundleException("Invalid bundle format", e);
538 }
539 }
540 final Bundle bundle = bundleContext.installBundle(path.toURI().toString());
541 if (bundleUninstalled)
542 {
543 refreshPackages();
544 }
545 return bundle;
546 }
547
548 public Bundle[] getBundles()
549 {
550 return bundleContext.getBundles();
551 }
552
553 public ServiceTracker getServiceTracker(final String clazz, final Set<ServiceTracker> trackedTrackers)
554 {
555 return new ServiceTracker(bundleContext, clazz, null)
556 {
557 @Override
558 public void close()
559 {
560 trackedTrackers.remove(this);
561 }
562 };
563 }
564
565 public List<HostComponentRegistration> getHostComponentRegistrations()
566 {
567 return hostComponentRegistrations;
568 }
569
570 void loadHostComponents(final DefaultComponentRegistrar registrar)
571 {
572
573 if (hostServicesReferences != null)
574 {
575 for (final ServiceRegistration reg : hostServicesReferences)
576 {
577 reg.unregister();
578 }
579 }
580
581
582 ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
583 try
584 {
585 Thread.currentThread().setContextClassLoader(initializedClassLoader);
586
587 hostServicesReferences = registrar.writeRegistry(bundleContext);
588 hostComponentRegistrations = registrar.getRegistry();
589 }
590 finally
591 {
592 Thread.currentThread().setContextClassLoader(oldCl);
593 }
594
595 }
596
597 private void extractAndInstallFrameworkBundles() throws BundleException
598 {
599 final List<Bundle> bundles = new ArrayList<Bundle>();
600 com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
601 for (final File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter()
602 {
603 public boolean accept(final File file, final String s)
604 {
605 return s.endsWith(".jar");
606 }
607 }))
608 {
609 bundles.add(install(bundleFile, false));
610 }
611
612 for (final Bundle bundle : bundles)
613 {
614 bundle.start();
615 }
616 }
617
618 public void refreshPackages()
619 {
620 final CountDownLatch latch = new CountDownLatch(1);
621 FrameworkListener refreshListener = new FrameworkListener()
622 {
623
624 public void frameworkEvent(FrameworkEvent event)
625 {
626 if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
627 {
628 log.info("Packages refreshed");
629 latch.countDown();
630 }
631 }
632 };
633
634 bundleContext.addFrameworkListener(refreshListener);
635 try
636 {
637 packageAdmin.refreshPackages(null);
638 boolean refreshed = false;
639 try
640 {
641 refreshed = latch.await(10, TimeUnit.SECONDS);
642 }
643 catch (InterruptedException e)
644 {
645
646 }
647 if (!refreshed)
648 {
649 log.warn("Timeout exceeded waiting for package refresh");
650 }
651 }
652 finally
653 {
654 bundleContext.removeFrameworkListener(refreshListener);
655 }
656 }
657
658 public void frameworkEvent(FrameworkEvent event)
659 {
660 String bundleBits = "";
661 if (event.getBundle() != null)
662 {
663 bundleBits = " in bundle " + event.getBundle().getSymbolicName();
664 }
665 switch (event.getType())
666 {
667 case FrameworkEvent.ERROR:
668 log.error("Framework error" + bundleBits, event.getThrowable());
669 break;
670 case FrameworkEvent.WARNING:
671 log.warn("Framework warning" + bundleBits, event.getThrowable());
672 break;
673 case FrameworkEvent.INFO:
674 log.info("Framework info" + bundleBits, event.getThrowable());
675 break;
676 }
677 }
678 }
679
680 }