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