Running a JavaFX script from an OSGi bundle

Two technologies that have been on my radar for a while are JavaFX, Sun’s entry in the RIA race, and OSGi, the Dynamic Module System for Java. In this tutorial, I will combine the two by running a JavaFX script from an OSGi bundle. You will need Eclipse and Java 6. [That’s right: a JavaFX tutorial that doesn’t want you to use NetBeans!]

Although JavaFX is still in the beta stage (despite being announced at JavaOne last year), and is very much in flux, it makes for an interesting UI technology. I wouldn’t want to write production ready applications in it yet, but maybe that will change in the not-too-distant future.

OSGi, on the other hand, is very much a mature technology. It is widely used, for instance for the plug-ins that make up Eclipse. In fact, Eclipse Equinox is now the reference implementation for OSGi. BTW, the OSGi term for plug-in is bundle, and I will use the two interchangeably.

Wrapping the JavaFX libraries in an OSGI bundle

Everything in OSGi happens inside a bundle, so the first step to running a JavaFX script from an OSGi bundle, is to create a bundle that holds the JavaFX jars. You will need to download the latest JavaFX distribution and unzip it somewhere.

Then, in Eclipse, select File|New|Project..., so that the New Project wizard appears. In the first step, select from the Plug-in Development category the Plug-in from existing JAR archives, and click Next. In the second step, click the Add External button and add all the jars from the JavaFX distribution’s lib directory. In the third step, enter JavaFX for the Project name, and click an OSGi framework under Target platform. Select standard for the OSGi framework, and click Finish. After a while, the plug-in project appears.

     

Next, open META-INF/MANIFEST.MF, select the MANIFEST.MF tab, and paste the following line into it:

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Now right-click on JRE System Library in the Package Explorer, and select Properties. For System library, select Execution environment, and from the combo box next to it, select JavaSE-1.6. Click OK.

 

Creating an OSGi bundle that will run a JavaFX script

You will now use the bundle you just created by a new bundle, the one running the JavaFX script. Again, select File|New|Project..., but this time, select Plug-in Project from the Plug-in Development category. In the next step, enter Run JavaFX for the Project Name, select the standard OSGi framework as the Target platform (same as above), and click Next. In the next step, just click Finish to create the project.

 

In the Manifest Editor, select the Dependencies tab. Click the Add button under Required Plug-ins, select JavaFX (1.0.0) from the list, and click the Save button in the toolbar.

   

Creating the JavaFX script

Create a new directory named script, and create a new file named HelloWorld.fx in it. Paste the following code into it:

import javafx.application.Frame;
import javafx.application.Stage;
import javafx.scene.paint.Color;
import javafx.scene.text.*;

Frame {
  title: "Hello World!"
  width: 550
  height: 200
  visible: true
  stage: Stage {
    content: Text {
      font: Font {
        name: "Sans Serif"
        style: FontStyle.BOLD
        size: 24
      }
      x: 20
      y: 40
      stroke: Color.BLUE
      fill: Color.BLUE
      content: "Hello from JavaFX Compiled Script"
    }
  }
}

   

Next, right-click the script directory, and select Build path|Use as Source Folder.

Running the script

To actually run the JavaFX script, we will use JSR-223: Scripting for the Java Platform. [That’s why I made you select JavaSE-1.6 as the bundle execution environment.]

In the Run JavaFX project, open the src directory, the run_javafx package, and then the Activator class. [A bundle’s activator allows you to do stuff when your bundle starts or stops.] In the start() method, add the following code:

final ScriptEngine engine = new ScriptEngineManager()
    .getEngineByExtension("fx");
final String scriptName = "HelloWorld.fx";
final InputStream stream = Thread.currentThread()
    .getContextClassLoader()
    .getResourceAsStream(scriptName);
engine.eval(new InputStreamReader(stream));

Use Eclipse’s Quick Fix to add the required import statements for ScriptEngine, ScriptEngineManager, InputStream, and InputStreamReader.

To run the bundle from Eclipse, select the MANIFEST.MF file, select the Overview tab, and click the Run button at the top.

In the Console, you will see the osgi> prompt, followed by a lot of error output. If you scroll through the output, you will see

org.osgi.framework.BundleException: Exception in
    run_javafx.Activator.start() of bundle Run_JavaFX.
[...]
Caused by: java.lang.NullPointerException
	at run_javafx.Activator.start(Activator.java:24)
[...]

Type exit + Enter in the Console to shut down the plug-in.

Finding the script engine

The NullPointerException comes from the ScriptEngine for JavaFX that could not be found. We can fix this as follows: create a new folder named services under META-INF, and copy the javax.script.ScriptEngineFactory file from the JavaFX project to it. See the Service Provider section in the Jar File Specification for an explanation.

 

Now when you run the plug-in, you will get

org.osgi.framework.BundleException: Exception in
    run_javafx.Activator.start() of bundle Run_JavaFX.
[...]
Caused by: javax.script.ScriptException: compilation failed
	at com.sun.tools.javafx.script.JavaFXScriptEngineImpl.parse(JavaFXScriptEngineImpl.java:254)
	at com.sun.tools.javafx.script.JavaFXScriptEngineImpl.eval(JavaFXScriptEngineImpl.java:144)
	at com.sun.tools.javafx.script.JavaFXScriptEngineImpl.eval(JavaFXScriptEngineImpl.java:135)
	at com.sun.tools.javafx.script.JavaFXScriptEngineImpl.eval(JavaFXScriptEngineImpl.java:140)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:232)
	at run_javafx.Activator.start(Activator.java:24)
[...]

There is nothing wrong with the JavaFX script, though. You can test this yourself by running the command line tools (javafxc and javafx) against it.

Fixing the class path

The problem is in the JavaFXScriptEngineImpl class. It passes the class path to the JavaFXScriptCompiler. But this class path is not the actual class path that is used by the class loader, it is taken from the java.class.path system property. You can override this by either setting the com.sun.tools.javafx.script.classpath system property, or by setting the classpath attribute on the ScriptContext.

I will take that last approach. Just before the engine.eval(...) line, add the following:

engine.getContext().setAttribute("classpath", 
    getJavafxClassPath(), ScriptContext.ENGINE_SCOPE);

Implementing the getJavafxClassPath() method is a bit tricky:

private String getJavafxClassPath() {
  String result = FX.class.getProtectionDomain()
      .getCodeSource().getLocation().toExternalForm();
  final int index = result.indexOf(":/");
  if (index >= 0) {
    result = result.substring(index + 1);
  }
  return result;
}

Now finally, when you run the plug-in, it will show the Hello world window:

19 thoughts on “Running a JavaFX script from an OSGi bundle

  1. i was able to compile a javafx class an instantiate it in java…

    in a plain test (in a static main method) i was able to initialize the class and it displays me the stage…

    but using the same method inside an osgi environment gave me the following:

    java.lang.NoClassDefFoundError: Could not initialize class com.sun.scenario.animation.AbstractMasterTimer
    at com.sun.scenario.animation.FrameJob.wakeUp(FrameJob.java:34)

    anybody saw this exception before?

    1. @Pydd

      java.lang.NoClassDefFoundError: Could not initialize class com.sun.scenario.animation.AbstractMasterTimer
      at com.sun.scenario.animation.FrameJob.wakeUp(FrameJob.java:34)

      Happened to me too. Any solution yet?

      1. Has anybody found a solution to this issue? I am at the same point than you both Pydd and Fausto. Thanks in advance.

  2. â—¦has anybody found a solution for that exception?

    java.lang.ClassCastException: com.sun.script.javascript.RhinoScriptEngineFactory cannot be cast to javax.script.ScriptEngineFactory

    any help is apreciated

    currently using javafx 1.3

  3. Hi all,

    i´ve got the same problem too, with javafx 1.2
    did someone found a solution for that!

    Thanks in advance

    1. has anybody found a solution for that exception?

      java.lang.ClassCastException: com.sun.script.javascript.RhinoScriptEngineFactory cannot be cast to javax.script.ScriptEngineFactory

      any help is apreciated

      currently using javafx 1.3

  4. hi all! I’ve get same exception as Draculo and Migi. Is there anybody who had success with current JavaFX 1.2.1 realease? Any help is appreciated!

  5. Hi!

    I’m trying to do the example but I have this error message in the final step:

    java.lang.ClassCastException: com.sun.script.javascript.RhinoScriptEngineFactory cannot be cast to javax.script.ScriptEngineFactory

    Could you help me?!

    Thank you very much!!

    1. This code was built using an old version of JavaFX. I haven’t tried it with the latest, sorry.

  6. Thanks for the interesting example!
    I wondered why you didn’t implement the BundleActivator directly into javaFX. This would simplify things a lot.

  7. Hi,
    i wanna do a dynamique desktop application .
    it means,that i have a server and in a client side,one desktop interface using javafx,and each time i wanna a component for example,it will be generated automatically when i call for it . do you think that i can use OSGI bundles to creat such a dynamism ?
    i know that for example,i can via a client FX have some dynamique data through an XML or Json Stream with http queries,but for having components and not data ? can u help me on this ?
    thanks in advance .

    1. This is not what OSGi is for. OSGi deals with how bundles interplay, not with how bundles are generated. But since bundles are just jars with special values in their manifest, you can generate bundles if you can generate jars. Also, since jars are binary, you can’t use XML or JSON to stream them easily.

  8. i wanted to wrap some components inside the javafx runtime too but the license prohibits you from distributing the jars, so if you make it an osgi bundle as we both did, you wouldn’t be able to use them.

    I think you could use it internally inside your own company though, but not to anyone outside of it.

  9. @Ransford: Sorry, I’m not familiar with Sling, so I can’t really help you. Having said that, nothing in the above is specific to Eclipse Equinox, so the bundles you create in this manner should also work with Felix.

  10. Hi,
    How can i configure javafx, with apache sling which uses apache felix as its osgi?

  11. This is pretty good. If you look back in the JFX mailing list histories, you’ll see me thrashing about quite a bit along similar lines. We solved things for the October-2007 build of JFX (still the fully interpreted version), but I am happy to see someone take on the compiled version. We’re moving there soon.

Leave a reply to Pydd Cancel reply