edvin / tornadofx

Lightweight JavaFX Framework for Kotlin
Apache License 2.0
3.68k stars 272 forks source link

OSGi Explanation #156

Closed edvin closed 8 years ago

edvin commented 8 years ago

As demonstrated in the OSGi screencast, TornadoFX let's a bundle export a View to other bundles by calling this function:

activator.registerView(MyView::class, "discriminator")

Under the covers, this implements an interface called ViewProvider and calls BundleContext#registerService with that implementation.

interface ViewProvider {
    fun getView(): UIComponent
    val discriminator: Any?
}

Here is the actual registerView function:

fun BundleContext.registerView(viewType: KClass<out UIComponent>, discriminator: Any? = null) {
    val provider = object : ViewProvider {
        override fun getView() = find(viewType)
        override val discriminator = discriminator
    }
    registerService(ViewProvider::class.java, provider, Hashtable<String, String>())
}

TornadoFX hides the need to implement this interface, but doesn't force you to abandon DS - you might just as well implement the interface manually. The choice is entirely up to the user, but you actually write less code by using the registerView extension function, and you don't need the extra build step so I suspect TornadoFX users will prefer the explicit way demonstrated in the screencast.

Granted, I'm no OSGi expert, and any input is greatly appreciated.

edvin commented 8 years ago

I realise that many will also want to use Declarative Services for other things in ther apps, and they might not want to use an Activator just to expose some Views. I'm adding a sample to the IntelliJ IDEA Plugin that shows how you would expose the View using declarative services instead. It will look something like this:

class PieChartView : View() {
    override val root = piechart("Imported Fruits") {
        data("Grapefruit", 12.0)
        data("Oranges", 25.0)
        data("Plums", 10.0)
        data("Pears", 22.0)
        data("Apples", 30.0)
    }

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

Here I'm implementing the ViewProvider interface and adding the @Component annotation. I've also update the dependencies and build config in the deployment descriptor for this bundle.

Does this look OK?

thomasnield commented 8 years ago

Was this initiative abandoned?

edvin commented 8 years ago

No, everything is implemented and working, and I got some replies on Twitter, but nothing here :)

sreeraaman commented 8 years ago

Dear All, I was going through the OSGi integration offered by TornadoFX. I got the sample application up and running on karaf by creating a feature for all the required bundles rather than manually copying them to the deploy folder as is the case with felix.

The feature file looks as follows:

`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

jfx-osgi-kotlin details
mvn:org.apache.httpcomponents/httpclient-osgi/4.5.2 mvn:commons-codec/commons-codec/1.9 mvn:org.apache.httpcomponents/httpcore-osgi/4.4.5 mvn:org.jetbrains.kotlin/kotlin-osgi-bundle/1.0.3 mvn:no.tornado/tornadofx/1.5.4 mvn:org.glassfish/javax.json/1.0.4 mvn:no.tornado/javafx-osgi/8.0
` However, one thing that I noticed is that after closing the stage, there is no way to restart the application without restarting the bundle. Also, I am not able to run multiple javafx application with one jvm instance. To the end user of the application, it becomes highly technical to understand all the nitty-gritties of osgi. I tried Platform.setImplicitExit(true) in the stop() function. However, the osgi framework is not yeilding. Can we allow the Platform.setImplicitExit(true) to actually stop the osgi instance so that the application can be re-launched via java -Dgosh.args=--nointeractive -jar bin/felix.jar to relauch the osgi container and bring up the application? best regards Sriraman.
edvin commented 8 years ago

Nice work on the feature file :)

You should be able to stop bundles that provide applications via the context.registerApplication hook, but there is a limitation in that you cannot stop the actual TornadoFX bundle.

The TornadoFX bundle already configures Platform.setImplicitExit(false). If you set it to true in your app then I suspect the the Platform exits and you're unable to start another application after that.

I'm not quite sure I understand what you want to do. Do you want to keep the bundle running, but unregister the app when the last window closes, is that it?

sreeraaman commented 8 years ago

Thanks for the quick response. I have not yet looked at the implementation of context.registerApplication / context.unregisterApplication extension functions.

I will give a little background on how to develop & deploy our javafx apps.

The app and its dependencies are deployed as shown below:

app.jar | lib | a.jar | b.jar

The lib/a.jar;lib/b.jar classpath entries are captured in the app.jar's manifest.mf. We launch the application via the normal java -jar app.jar. A shortcut is created and placed on the desktop of the user for him to launch the app whenever required.

When the main stage is closed we do not close the app, but we put it in the system tray with options to show & close. On click the show, the stage is brought back and shown to the user. On click of close button, we close the app itself.

I am not sure if the same behavior of customer initiating a start / stop of the application is feasible in osgi parlance. Start typically happens when the bundle is activated and stop when the bundle is stopped or osgi framework is shutdown. If the user has closed the stage by mistake, he has to restart the osgi framework or manually stop the bundle (providing the application) and start that bundle again.

Is there a possibility of exposing the context.registerApplication / context.unregisterApplication via jmx or something so that we can invoke to same from outside the framework ?

best regards Sriraman.

edvin commented 8 years ago

Ah, yes that could be possible. Let me have a look early next week and get back to you :)

edvin commented 8 years ago

Just so I know I understand your requirement correctly.. If the user closes the window, you close the stage, and the app still runs.

In this case, are you able to show your app again, or aren't you?

Since the app is still running, you should be able to call FX.primaryStage.show() and be back in business.

In your case when the user actively closes the application, you want to shut down the bundle or shut down the whole OSGi environment?

sreeraaman commented 8 years ago

Edvin,

To reproduce my behavior, you can do the following:

  1. create a tornadofx-maven-osgi-project from intellij idea IDE.
  2. build and drop the jar file in the deploy folder (felix / karaf).
  3. start the bundle from felix gogo shell
  4. The application ui comes up.
  5. close the stage by clicking on the close icon.
  6. The app closes.
  7. How do I bring back the app . ?
  8. As of now, the only way to bring it back is to stop the bundle and start the bundle again.

We do not show the osgi console to the end users. It is only for development purposes. Mostly, osgi runtime itself runs as a system service. (we use karaf)

There is no external control in terms of the starting the app. The app starts when the bundle gets activated. Are there any other ways of starting / stopping the app without getting into the osgi shell.?

Hope this answers your question.

Also, as you mentioned, from where do I make the call to FX.primaryStage.show() ?

edvin commented 8 years ago

Wouldn't this be the exact same problem without OSGi? Without an UI, how would you reopen the app window for the user? Could you install a tray icon to allow the user to reopen the app window through a tray icon action? If not, where do you envision the user interaction that should reopen the window to accur?

sreeraaman commented 8 years ago

Hey,

In a non-osgi environment, we currently do the following. On close of stage by clicking on the close button the stage.setOnCloseRequest is fired and we hide the stage. However, we have a system tray icon with a popup menu having two more options (show /close). If show is selected, we show the stage again. and if close is selected we end the jvm via System.exit(0). The app is launched via java -jar app.jar. Hence, after the close, if the user wants to relaunch the app, he / she can do so by clicking on the desktop short cut again which launches the jvm and launches the main class which is a javafx application class.

I am just wondering if something like this can be reproduced on an osgi environment.

edvin commented 8 years ago

Ah, I see. So the tray icon close action stops the bundle, and then you want to relaunch that bundle by starting another process, but keep the same JVM.

I can see a number of ways to solve that. One would be to launch your OSGi environment via a custom main method and use JUnique to detect if you should start the OSGi environment or simply send a signal to the running JVM so it can restart the bundle.

Would that solve it, or is it too clunky? I have some other ideas as well, but this should be fairly easy to configure at least.

edvin commented 8 years ago

Also, you do want to keep the OSGi container running after the UI exits, right? But you didn't do this prior to introducing OSGi, right?

sreeraaman commented 8 years ago

We have not ported our app on an osgi environment yet. What we intend to do as part of the osgi port is to achieve the following:

  1. To run the osgi container as a system service. At this point we do not want the java fx app to be started.
  2. Provide a desktop shortcut for the user to launch the javafx osgi app. here we should to be able to dynamically register the app via context.registerApp call. (However for a non-osgi app to obtain a reference to the bundlecontext is not easy). May be we should have a JMX bean or some other mechanism by which the app is registered. Probably, there should be a way to figure out if the app is already registered or not. If it is already registered, just show the stage via FX.primaryStage.show() as indicated.
  3. After the stage is shown, and if the user chooses to close the app by clicking the close button, handle the stage.setOnCloseRequest event within which the app should get unregistered. (hopefully possible)
  4. To show the Application ui again, the user will click on the desktop shortcut again which repeats step 2-4.
  5. While all of these happen, the osgi runtime should be up and running.
  6. Essentially there will be an intermediate launcher app that does the registration of the target application in the osgi context and self destroys itself. Post registration of the target app in the osgi context, the target application ui is automatically shown.

Does these steps look okay. ?

edvin commented 8 years ago

Yes, this looks OK and is most probably not too hard to achieve. You are right about that you need a launcher in front of the OSGi environment.

This is far easier to achieve if you actually make that launcher so that it starts the OSGi environment embedded. Then it could start it if it receives an initial request, or just start the right bundle to get the UI back up etc. As I mentioned, JUnique is a library that makes this very simple. If you don't want to depend on a third party lib, it should be fairly easy to write this yourself.

Alternatively, have one bundle listen on a port and send signals to that so it can start/stop the right bundles.

The TornadoFX bundle will keep the JavaFX Platform running so that you can register and unregister TornadoFX apps at will. The rest is just plumbing around these concepts.

I don't think any special support in TornadoFX will make this easier, lest you have something specific in mind. However, if you need help with sorting out how to do this, I'd be happy to help, but I would need a couple of days before I have time. Need to finish some documentation for TornadoFX first, and kick off the shiny new KDBC framework :)

I would start with embedding Felix, as that is quite straight forward and well documented. What do you think?

sreeraaman commented 8 years ago

Thanks for your time and suggestions. As such I am new to both Kotlin and tornadofx. I will also go through the tornadofx documentation and see how to achieve what was described earlier. Will trouble you as and when I am stuck. Hope you don't mind. :)

edvin commented 8 years ago

Great, please ask if anything is unclear or missing from the guide, or if you get stuck in any way. We'll help for sure :)