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