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