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.osgi.util.OsgiHeaderUtil;
14 import com.atlassian.plugin.util.ClassLoaderUtils;
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.*;
25 import org.osgi.service.packageadmin.PackageAdmin;
26 import org.osgi.util.tracker.ServiceTracker;
27
28 import java.io.File;
29 import java.io.FilenameFilter;
30 import java.io.IOException;
31 import java.net.URL;
32 import java.util.*;
33 import java.util.concurrent.CopyOnWriteArraySet;
34 import java.util.jar.JarFile;
35
36
37
38
39 public class FelixOsgiContainerManager implements OsgiContainerManager
40 {
41 private static final Log log = LogFactory.getLog(FelixOsgiContainerManager.class);
42 public static final String OSGI_FRAMEWORK_BUNDLES_ZIP = "osgi-framework-bundles.zip";
43 private static final String OSGI_BOOTDELEGATION = "org.osgi.framework.bootdelegation";
44 private static final String ATLASSIAN_PREFIX = "atlassian.";
45 private BundleRegistration registration = null;
46 private Felix felix = null;
47 private boolean felixRunning = false;
48 private File cacheDirectory;
49 private boolean disableMultipleBundleVersions = true;
50
51 private final URL frameworkBundlesUrl;
52 private PackageScannerConfiguration packageScannerConfig;
53 private File frameworkBundlesDir;
54 private HostComponentProvider hostComponentProvider;
55 private Logger felixLogger;
56 private final Set<ServiceTracker> trackers;
57
58
59
60
61
62
63
64
65 public FelixOsgiContainerManager(File frameworkBundlesDir, PackageScannerConfiguration packageScannerConfig,
66 HostComponentProvider provider, PluginEventManager eventManager)
67 {
68 this(ClassLoaderUtils.getResource(OSGI_FRAMEWORK_BUNDLES_ZIP, FelixOsgiContainerManager.class), frameworkBundlesDir,
69 packageScannerConfig, provider, eventManager);
70 }
71
72
73
74
75
76
77
78
79
80 public FelixOsgiContainerManager(URL frameworkBundlesZip, File frameworkBundlesDir,
81 PackageScannerConfiguration packageScannerConfig, HostComponentProvider provider,
82 PluginEventManager eventManager)
83 {
84 Validate.notNull(frameworkBundlesZip, "The framework bundles zip is required");
85 Validate.notNull(frameworkBundlesDir, "The framework bundles directory must not be null");
86 Validate.notNull(packageScannerConfig, "The package scanner configuration must not be null");
87 Validate.notNull(eventManager, "The plugin event manager is required");
88
89 this.frameworkBundlesUrl = frameworkBundlesZip;
90 this.packageScannerConfig = packageScannerConfig;
91 this.frameworkBundlesDir = frameworkBundlesDir;
92 this.hostComponentProvider = provider;
93 this.trackers = Collections.synchronizedSet(new HashSet<ServiceTracker>());
94 eventManager.register(this);
95 felixLogger = new FelixLoggerBridge(log);
96 }
97
98 public void setFelixLogger(Logger logger)
99 {
100 this.felixLogger = logger;
101 }
102
103 public void setDisableMultipleBundleVersions(boolean val)
104 {
105 this.disableMultipleBundleVersions = val;
106 }
107
108 @PluginEventListener
109 public void onStart(PluginFrameworkStartingEvent event)
110 {
111 start();
112 }
113
114 @PluginEventListener
115 public void onShutdown(PluginFrameworkShutdownEvent event)
116 {
117 stop();
118 }
119
120
121 public void start() throws OsgiContainerException
122 {
123 if (isRunning())
124 return;
125
126 detectIncorrectOsgiVersion();
127
128 initialiseCacheDirectory();
129
130 DefaultComponentRegistrar registrar = collectHostComponents(hostComponentProvider);
131
132 final StringMap configMap = new StringMap(false);
133
134 configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");
135
136
137 configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, OsgiHeaderUtil.determineExports(registrar.getRegistry(), packageScannerConfig));
138
139
140 configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, cacheDirectory.getAbsolutePath());
141
142 configMap.put(FelixConstants.LOG_LEVEL_PROP, String.valueOf(felixLogger.getLogLevel()));
143 String bootDelegation = getAtlassianSpecificOsgiSystemProperty(OSGI_BOOTDELEGATION);
144 if (bootDelegation == null || bootDelegation.trim().length() == 0)
145 {
146 bootDelegation = "weblogic.*,META-INF.services,com.yourkit.*,com.jprofiler.*,org.apache.xerces.*";
147 }
148
149 configMap.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegation);
150 if (log.isDebugEnabled())
151 {
152 log.debug("Felix configuration: "+configMap);
153 }
154
155 try
156 {
157
158 registration = new BundleRegistration(frameworkBundlesUrl, frameworkBundlesDir, registrar);
159 final List<BundleActivator> list = new ArrayList<BundleActivator>();
160 list.add(registration);
161
162
163
164 felix = new Felix(felixLogger, configMap, list);
165
166
167 Runnable start = new Runnable() {
168 public void run() {
169 try
170 {
171 felix.start();
172 felixRunning = true;
173 } catch (BundleException e)
174 {
175 throw new OsgiContainerException("Unable to start felix", e);
176 }
177 }
178 };
179 Thread t = new Thread(start);
180 t.setDaemon(true);
181 t.start();
182
183
184 t.join(10 * 60 * 1000);
185 }
186 catch (Exception ex)
187 {
188 throw new OsgiContainerException("Unable to start OSGi container", ex);
189 }
190 }
191
192
193
194
195 private void detectIncorrectOsgiVersion()
196 {
197 try
198 {
199 Bundle.class.getMethod("getBundleContext");
200 }
201 catch (NoSuchMethodException e)
202 {
203 throw new OsgiContainerException("Detected older version (4.0 or earlier) of OSGi. If using WebSphere "+
204 "6.1, please enable application-first (parent-last) classloading and the 'Single classloader for "+
205 "application' WAR classloader policy.");
206 }
207 }
208
209 public void stop() throws OsgiContainerException
210 {
211 try
212 {
213 if (felixRunning)
214 {
215 for (ServiceTracker tracker : new HashSet<ServiceTracker>(trackers))
216 tracker.close();
217 felix.stopAndWait();
218 }
219
220 if (cacheDirectory != null && cacheDirectory.exists()) {
221 FileUtils.deleteDirectory(cacheDirectory);
222 }
223
224 felixRunning = false;
225 felix = null;
226 } catch (IOException e)
227 {
228 throw new OsgiContainerException("Unable to stop OSGi container", e);
229 }
230 }
231
232 public Bundle[] getBundles()
233 {
234 if (isRunning())
235 {
236 return registration.getBundles();
237 }
238 else
239 {
240 throw new IllegalStateException("Cannot retrieve the bundles if the Felix container isn't running. Check" +
241 " earlier in the logs for the possible cause as to why Felix didn't start correctly.");
242 }
243
244
245 }
246
247 public ServiceReference[] getRegisteredServices()
248 {
249 return felix.getRegisteredServices();
250 }
251
252 public ServiceTracker getServiceTracker(String cls)
253 {
254 if (!isRunning())
255 throw new IllegalStateException("Unable to create a tracker when osgi is not running");
256
257 ServiceTracker tracker = registration.getServiceTracker(cls, trackers);
258 tracker.open();
259 trackers.add(tracker);
260 return tracker;
261 }
262
263 public Bundle installBundle(File file) throws OsgiContainerException
264 {
265 try
266 {
267 return registration.install(file, disableMultipleBundleVersions);
268 } catch (BundleException e)
269 {
270 throw new OsgiContainerException("Unable to install bundle", e);
271 }
272 }
273
274 DefaultComponentRegistrar collectHostComponents(HostComponentProvider provider)
275 {
276 DefaultComponentRegistrar registrar = new DefaultComponentRegistrar();
277 if (provider != null)
278 provider.provide(registrar);
279 return registrar;
280 }
281
282 void initialiseCacheDirectory() throws OsgiContainerException
283 {
284 try
285 {
286 cacheDirectory = File.createTempFile("felix", null);
287 cacheDirectory.delete();
288 if (cacheDirectory.exists())
289 FileUtils.deleteDirectory(cacheDirectory);
290
291 cacheDirectory.mkdir();
292 } catch (IOException e)
293 {
294 throw new OsgiContainerException("Cannot create cache directory", e);
295 }
296 }
297
298 public boolean isRunning()
299 {
300 return felixRunning;
301 }
302
303 public List<HostComponentRegistration> getHostComponentRegistrations()
304 {
305 return registration.getHostComponentRegistrations();
306 }
307
308 private String getAtlassianSpecificOsgiSystemProperty(String originalSystemProperty)
309 {
310 return System.getProperty(ATLASSIAN_PREFIX + originalSystemProperty);
311 }
312
313
314
315
316
317 static class BundleRegistration implements BundleActivator, BundleListener
318 {
319 private BundleContext bundleContext;
320 private DefaultComponentRegistrar registrar;
321 private List<ServiceRegistration> hostServicesReferences;
322 private List<HostComponentRegistration> hostComponentRegistrations;
323 private URL frameworkBundlesUrl;
324 private File frameworkBundlesDir;
325 private PackageAdmin packageAdmin;
326
327 public BundleRegistration(URL frameworkBundlesUrl, File frameworkBundlesDir, DefaultComponentRegistrar registrar)
328 {
329 this.registrar = registrar;
330 this.frameworkBundlesUrl = frameworkBundlesUrl;
331 this.frameworkBundlesDir = frameworkBundlesDir;
332 }
333
334 public void start(BundleContext context) throws Exception {
335 this.bundleContext = context;
336 ServiceReference ref = context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
337 packageAdmin = (PackageAdmin) context.getService(ref);
338
339 context.addBundleListener(this);
340
341 loadHostComponents(registrar);
342 extractAndInstallFrameworkBundles();
343 context.addFrameworkListener(new FrameworkListener()
344 {
345 public void frameworkEvent(FrameworkEvent event)
346 {
347 String bundleBits = "";
348 if (event.getBundle() != null)
349 bundleBits = " in bundle "+event.getBundle().getSymbolicName();
350 switch (event.getType())
351 {
352 case FrameworkEvent.ERROR : log.error("Framework error"+bundleBits, event.getThrowable());
353 break;
354 case FrameworkEvent.WARNING : log.warn("Framework warning"+bundleBits, event.getThrowable());
355 break;
356 case FrameworkEvent.INFO : log.info("Framework info"+bundleBits, event.getThrowable());
357 break;
358 }
359 }
360 });
361 }
362
363 public void stop(BundleContext ctx) throws Exception {
364 }
365
366 public void bundleChanged(BundleEvent evt) {
367 switch (evt.getType()) {
368 case BundleEvent.INSTALLED:
369 log.info("Installed bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
370 break;
371 case BundleEvent.RESOLVED:
372 log.info("Resolved bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
373 break;
374 case BundleEvent.UNRESOLVED:
375 log.info("Unresolved bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
376 break;
377 case BundleEvent.STARTED:
378 log.info("Started bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
379 break;
380 case BundleEvent.STOPPED:
381 log.info("Stopped bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
382 break;
383 case BundleEvent.UNINSTALLED:
384 log.info("Uninstalled bundle " + evt.getBundle().getSymbolicName() + " ("+evt.getBundle().getBundleId()+")");
385 break;
386 }
387 }
388
389 public Bundle install(File path, boolean uninstallOtherVersions) throws BundleException
390 {
391 if (uninstallOtherVersions)
392 {
393 try
394 {
395 JarFile jar = new JarFile(path);
396 String name = jar.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
397 for (Bundle oldBundle : bundleContext.getBundles())
398 {
399 if (name.equals(oldBundle.getSymbolicName()))
400 {
401 log.info("Uninstalling existing version "+oldBundle.getHeaders().get(Constants.BUNDLE_VERSION));
402 oldBundle.uninstall();
403
404
405
406
407 }
408 }
409
410 } catch (IOException e)
411 {
412 throw new BundleException("Invalid bundle format", e);
413 }
414 }
415 Bundle bundle = bundleContext.installBundle(path.toURI().toString());
416 return bundle;
417 }
418
419 public Bundle[] getBundles()
420 {
421 return bundleContext.getBundles();
422 }
423
424 public ServiceTracker getServiceTracker(String clazz, final Set<ServiceTracker> trackedTrackers)
425 {
426 return new ServiceTracker(bundleContext, clazz, null){
427 @Override
428 public void close()
429 {
430 trackedTrackers.remove(this);
431 }
432 };
433 }
434
435 public List<HostComponentRegistration> getHostComponentRegistrations()
436 {
437 return hostComponentRegistrations;
438 }
439
440 private void loadHostComponents(DefaultComponentRegistrar registrar)
441 {
442
443 if (hostServicesReferences != null) {
444 for (ServiceRegistration reg : hostServicesReferences)
445 reg.unregister();
446 }
447
448
449 hostServicesReferences = registrar.writeRegistry(bundleContext);
450 hostComponentRegistrations = registrar.getRegistry();
451 }
452
453 private void extractAndInstallFrameworkBundles() throws BundleException
454 {
455 List<Bundle> bundles = new ArrayList<Bundle>();
456 com.atlassian.plugin.util.FileUtils.conditionallyExtractZipFile(frameworkBundlesUrl, frameworkBundlesDir);
457 for (File bundleFile : frameworkBundlesDir.listFiles(new FilenameFilter() {
458 public boolean accept(File file, String s) {
459 return s.endsWith(".jar");
460 }
461 }))
462 {
463 bundles.add(install(bundleFile, false));
464 }
465
466 for (Bundle bundle : bundles)
467 {
468 bundle.start();
469 }
470 }
471 }
472
473 }