JavaFX Script and other musing Clarkeman's Weblog

Thursday Jul 30, 2009

In the book, JavaFX - Developing Rich Internet Applications, I wrote a Code Recipe on how to include a JMS feed into a JavaFX application. The basic premise is that when the JMS onMessage method is called on the javax.jms.MessageListener interface, the calling thread will be from the JMS framework, so this message needs to be pushed over to the main JavaFX thread using com.sun.javafx.runtime.Entry.deferAction( Runnable ). Here is the example from the book from Subscriber.java:

@Override
public void onMessage(final Message msg) {
    try {
            // must run this on the JavaFX Main thread
            // If you don't you will eventually get exceptions
            // in the JavaFX code.
            Entry.deferAction( new Runnable() {
                @Override
                public void run() {
                    fxListener.onMessage(msg);
                }
            } );

    } catch (Exception ex) {
            Logger.getLogger(Subscriber.class.getName()).log(Level.SEVERE, null, ex);
    }
}

This all runs fine on my machine when I spit out messages, 1 per millisecond. However, I was working with a colleague here at Sun who was using this code pattern with a real live log server, and when he cranked it up, he started to get Null Pointer Exceptions when he fetched from  the Message object within the JavaFX main thread.

This perplexed us for a while, and on the intuition that the JMS framework may have been reusing the Message object on subsequent messages, we inserted logic in the Java onMessage() code block to fetch the message contents before pushing them into the JavaFX main thread. Voila, the Null Pointer Exceptions stopped. Evidently, we were encountering a race condition where the JMS Framework was reusing or clearing the Message object faster than the JavaFX thread could consume it.  This behavior from the JMS framework was unexpected, but I guess it is an optimization for performance.

To overcome this race condition, it is necessary to make a copy of the message data prior to the javax.jms.MessageListener.onMessage() method returning. As a result, I have now modified the example to look like this:

    if(msg instanceof TextMessage ) {
        final Message copy = topicSession.createTextMessage(((TextMessage)msg).getText());⁞
        Entry.deferAction( new Runnable() {
            @Override
            public void run() {
                fxListener.onMessage(copy);
            }
        } );
    }

I will be updating the example from the book. But the lesson learned from this is to be wary when using frameworks that you have no control over.



Sunday Jul 05, 2009

At one of my sessions showing off JavaFX, I was asked the question, how can I run JavaFX in my Swing Application? At first, I asked why do you want to do this? The answer was we have built up the Swing application over many years and cannot rewrite it all at once, but we would like to take advantage of  JavaFX's features. This sounded reasonable to me.

So I did some investigation and found that there is a blog about this at http://blogs.sun.com/javafx/entry/how_to_use_javafx_in. However, this only covered JavaFX 1.1. Now, that JavaFX 1.2 is out, this is no longer applicable. Here is how to do it in JavaFX 1.2.

We start with the obligatory warning, this is only for the Desktop profile, and do not try this at home unless you are a highly trained professional. This, like the previous way to do this, is a hack. It uses private APIs in JavaFX and is subject to change with any new release.

First, we need to pull the underlying Swing Component out from the Scene class. To do this, we need to use JavaFX reflection from a Java class. This is done by passing in the name of the Scene class, creating an object for it via reflection, then locating its underlying Swing implementation, and finally pulling out the Scenegraph JSGPanel, that is a Swing Component. The following listing shows how to do this.


package scene;

import com.sun.javafx.tk.swing.SwingScene;
import com.sun.scenario.scenegraph.JSGPanel;
import javafx.reflect.*;
import javafx.reflect.FXLocal.ObjectValue;

public class LoadScene {
    private static FXLocal.Context context = FXLocal.getContext();

    public static JSGPanel loadScene(String classname) {
        FXClassType classRef = context.findClass(classname);
        FXLocal.ObjectValue obj = (ObjectValue) classRef.newInstance();
        FXFunctionMember getPeer = classRef.getFunction("impl_getPeer");
        FXLocal.ObjectValue peer = (ObjectValue) getPeer.invoke(obj);
        SwingScene scene = (SwingScene)peer.asObject();

        return scene.scenePanel;
    }
}

Next, to use this in a Swing application, we create the Swing Components normally, leaving a JPanel that will hold the JSGPanel that represents the JavaFX scene. An example using a JavaFX scene, "scene.MyScene", that runs a simple animation is:


public class TheFrame extends javax.swing.JFrame {

    /** Creates new form TheFrame */
    public TheFrame() {
        initComponents();⁞
        jPanel1.add(LoadScene.loadScene("scene.MyScene"), BorderLayout.CENTER);
    }⁞
                       
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("JFrame - JavaFX Panel Test");
        getContentPane().setLayout(new java.awt.FlowLayout());

        jPanel1.setLayout(new java.awt.BorderLayout());
        getContentPane().add(jPanel1);

        pack();
    }                

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TheFrame().setVisible(true);
            }
        });
    }                   
    private javax.swing.JPanel jPanel1;                  
}

To complete the exercise, we need to add the JavaFX jar files (including javafxc.jar) to the classpath, then use "java" to start the application.

Here is the picture of the JavaFX scene running within the Swing JFrame. This simple JavaFX application shows an animation of "Hello World" moving across the scene.

Swing hosting JavaFX scene

I have contributed an implementation based on this class to the JFXtras project at http://code.google.com/p/jfxtras/. It is now included in JFXtras release 0.5 and the class to use is org.jfxtras.scene.SceneToJComponent.