Skip to content
This repository has been archived by the owner on Sep 28, 2024. It is now read-only.
Edvin Syse edited this page Sep 1, 2016 · 26 revisions

WikiDocumentationOSGi

OSGi

Creating highly modular and dynamic applications on the JVM is made possible by OSGi - a dynamic module system for Java. Feel free to skip this chapter for now if you're not interested in OSGi technology.

Requirements

To run TornadoFX in an OSGi container, you need to load the following bundles. Usually this is a matter of dumping these jars into the bundle directory. Note that any jar that is to be used in an OSGi container needs to be "OSGi enabled". This means adding some OSGi specific entries the META-INF/MANIFEST.MF file. All the resources below are OSGi enabled, including the TornadoFX jar.

Click here to download a distribution with all the required bundles already installed into Apache Felix 5.4.0.

Artifact Version Binary
JavaFX 8 OSGi Support 8.0 jar
TornadoFX 1.5.5 jar
Kotlin OSGI Bundle* 1.0.3 jar
Configuration Admin** 1.8.10 jar
Commons Logging 1.2 jar
Apache HTTP-Client 4.5.2 jar
Apache HTTP-Core 4.4.5 jar
JSON 1.0.4 jar

* The Kotlin OSGi bundle contains special versions of kotlin-stdlib and kotlin-reflect with the required OSGi manifest information.

** This links to the Apache Felix implementation of the OSGi Config Admin interface. Feel free to use the implementation from your OSGi container instead. Some containers, like Apache Karaf, already has the Config Admin bundle loaded, so you won't need it there.

Prerequisite knowledge

You should be familiar with the basics of OSGi before you continue. To get a quick overview of OSGi technology you can check out the tutorials on the OSGi Alliance website. The Apache Felix tutorials are also a good reference for basic OSGi patterns.

Dynamic TornadoFX

When the TornadoFX bundle is loaded into an OSGi container, it starts listening for other bundles that provides services to TornadoFX. These services can be any of the following type:

TornadoFX Dynamic Application

You can tell the TornadoFX OSGi Runtime to automatically start your application when it is loaded. This is done by registering your application in your bundle Activator:

class Activator : BundleActivator {
    override fun start(context: BundleContext) {
        context.registerApplication(MyApp::class)
    }

    override fun stop(context: BundleContext) {
    }
}

If you prefer OSGi declarative services instead, this will have the same effect provided that you have the OSGi DS bundle loaded:

@Component
class AppRegistration : ApplicationProvider {
    override val application = MyApp::class
}

Provided that the TornadoFX bundle is available in your container, this is enough to start your application automatically. You can also stop and start it, something that is normally not possible in a JavaFX environment. This is possible because TornadoFX creates a ProxyApplication that forwards requests to the application provided by your bundle. You can even unload one application and load another without stopping the JVM.

TornadoFX Dynamic Stylesheet

You can provide type safe stylesheets to other TornadoFX bundles by registering them in the Activator:

class Activator : BundleActivator {
    override fun start(context: BundleContext) {
        context.registerStylesheet(Styles::class)
    }

    override fun stop(context: BundleContext) {
    }
}

Using OSGi Declarative Services the registration looks like this:

@Component 
class StyleRegistration : StylesheetProvider {
    override val stylesheet = Styles::class
}

Whenever this bundle is loaded, every active View will have this stylesheet applied. When the bundle is unloaded, the stylesheet is automatically removed. If you want to provide multiple stylesheets based on the same style classes, it is a good idea to create one bundle that exports the cssclass definitions, so that your Views can reference these styles, and the stylesheet bundles can create selectors based on them.

TornadoFX Dynamic View

A very cool aspect of OSGi is the ability to have UI elements pop up when they become available. A typical use case could be a "dashboard" application. The base application bundle contains a View that can hold other Views, and tells the TornadoFX OSGi Runtime that it would like to automatically embed Views if they meet certain criteria.

This is a View that contains a VBox. It tells the TornadoFX OSGi Runtime that it would like to have other Views embedded into it if they are tagged with the discriminator dashboard:

class Dashboard : View() {
    override val root = VBox()

    init {
        title = "Dashboard Application"
        addViewsWhen { it.discriminator == "dashboard" }
    }
}

If the addViewsWhen function returns true, the View is added to the VBox. To offer up Views to this Dashboard, another bundle would declare that it wants to export it's View by setting the dashboard discriminator:

class Activator : BundleActivator {
    override fun start(context: BundleContext) {
        context.registerView(MusicPlayer::class, "dashboard")
    }

    override fun stop(context: BundleContext) {
    }
}

Again, the OSGi Declarative Services way of exporting the View would look like this:

@Component
class MusicPlayerRegistration : ViewProvider {
    override val discriminator = "dashboard"
    override fun getView() = find(MusicPlayer::class)
}

The addViewsWhen function is smart enough to inspect the VBox and find out how to add the child View it was presented. It can also figure out that if you call the function on a TabPane it would create a new Tab and set the title to the child View title etc. If you would like to do something custom with the presented Views, you can return false from the function so that the child View will not be added automatically and then do whatever you want with it. Even though the Tab example is supported out of the box, you could do it explicitly like this:

tabPane.addViewsWhen { 
    if (it.discriminator == "dashboard") {
        val view = it.getView()
        tabPane.tab(view.title, view.root)
    }
    false
}

Manual handling of dynamic Views

Create your first OSGi bundle

A good starting point is the tornadofx-maven-osgi-project template in the TornadoFX IntelliJ IDEA plugin. This contains everything you need to automatically build OSGi bundles from your sources. The OSGI IDEA plugin makes it very easy to setup and run an OSGi container directly from the IDE. There is a screencast at https://www.youtube.com/watch?v=liOFCH5MMKk that shows these concepts in action.

Currently this is the extent of the specialised support for OSGi in the TornadoFX framework. As always, TornadoFX doesn't obstruct you from doing everything manually, but provides some convenient and elegant patterns to help you smoothly integrate OSGi into your applications.

OSGi Console

TornadoFX has a built in OSGi console from which you can inspect bundles, change their state and even install new bundles with drag and drop. You can bring up the console with Alt-Meta-O or configure another shortcut by setting FX.osgiConsoleShortcut or programmatically opening the OSGIConsole View.

Next: i18n

Clone this wiki locally