This chapter is geared primarily towards folks who already have familiarity with OSGi, which stands for Open Services Gateway Initiative. The idea behind OSGi is adding and removing modules to a Java application without the need for restarting. TornadoFX supports OSGi and allows highly modular and dynamic applications.
If you have no interest in OSGi currently, you are welcome to skip this chapter. However, it is highly recommended to at least know what it is so you can identify moments in the future that make it handy.
TornadoFX comes with the metadata needed for an OSGi runtime to detect and enable it. When the tornadofx.jar
is
loaded in an OSGi container, a number of services are automatically installed in the runtime. These services enable
some very interesting features which we will discuss.
Please be familiar with the basics of OSGi before you continue this chapter. 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 starting point reference for basic OSGi patterns.
When the TornadoFX JAR is loaded, you can create your own TornadoFX bundle and create your
application any way you like. However, some usage patterns are so typical and useful that TornadoFX has built-in
support for them.
The dynamic nature of OSGi lends itself well to GUI applications in general. The ability to have certain
functionality come and go as the environment changes can be powerful. JavaFX itself is unfortunately written in
a way that prevents you from starting another JavaFX application after the initial application shuts down. To
circumvent this shortcoming and enable you to stop and start your application as many times as you want, TornadoFX
provides a way to register your App
class with an application proxy which will keep the JavaFX environment running
even when your application shuts down.
To get started, implement a BundleActivator
that provides a means to start()
and stop()
an App
. Registering your application for this functionality can be done by calling context.registerApplication
with your
App
class as the single parameter 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 once the bundle is activated. You can now stop and start it as many times as you like by stopping and
starting the bundle.
You can provide type-safe stylesheets to other TornadoFX bundles by registering them in an 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.
A 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. In this example, 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.
For instance, we can create a View
that contains a VBox
. We tell the TornadoFX OSGi Runtime that we 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. Here we register
a fictive MusicPlayer
view to be docked into the dashboard when it's bundle becomes active.
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
A good starting point is the tornadofx-maven-osgi-project
template in the TornadoFX IntelliJ IDEA plugin. This
contains everything you need to 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.
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.
To run TornadoFX in an OSGi container, you need to load the required bundles. Usually this is a matter of dumping
these jars into the bundle
directory of the container. Note that any jar that is to be used in an OSGi container
needs to be "OSGi enabled", which effectively means adding some OSGi specific entries the META-INF/MANIFEST.MF
file.
We provided a complete installation with Apache Felix and TornadoFX already installed at
http://tornadofx.io/#felix.
These are the required artifacts for any TornadoFX application running in an OSGi container. Your container might
already be bundle with some of these, so check the container documentation for further details.
Artifact | Version | Binary |
---|---|---|
JavaFX 8 OSGi Support | 8.0 | jar |
TornadoFX | 1.7.12 | jar |
Kotlin OSGI Bundle* | 1.5.11 | 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.