The Enterprise Side of JavaFX

By Adam Bien

Learn to use LightView to convert REST services into a bindable set of properties.

Published June 2012

Downloads:

Download: Java FX

Download: GlassFish

Download: LightFish

Introduction

JavaFX 2 is Java. JavaFX 2 is going to be shipped and packaged with the JDK. Unlike the first version, JavaFX 2 is targeted as an official Swing successor with a focus on business and enterprise applications. In this three-part series, I would like to show you the “enterprisey” side of JavaFX with a minimal amount of animations, effects, and transitions and a focus on structuring the presentation logic and integration with back-end services.

LightFish—A Real-World Sample

Oracle GlassFish Server 3.1 is a Java Platform, Enterprise Edition (Java EE) 6 application server with extensive monitoring capabilities that makes its data available through multiple channels. (The same is true for GlassFish Server Open Source Edition.) The most accessible channel is REST services: You need only an HTTP client or a command line utility, such as cURL or Wget, to access the monitoring statistics. Such data is particularly valuable for postmortem analysis of “successful” stress tests (that is, tests that show a dead server). The only problem is there is no history. Only the most recent monitoring data is available. In the case of nightly runs, historical data would provide you with the ability to analyze the trends and overall robustness of the application.

LightFish is an open source monitoring application that periodically fetches and persists snapshots from a “GlassFish Under Test” machine (see Figure 1) and makes them available in real time via a simplified REST API.
Figure 1: LightFish Deployment

Figure 1: LightFish Deployment

LightFish comes with a basic Web interface to manage the data-capturing interval that is implemented with JavaServer Faces 2. LightView is a JavaFX 2 real-time visualizer that integrates the Web UI directly and accesses the monitoring data via REST and long polling. It could be considered to be a “stress test dashboard.”

Figure 2: LightView Dashboard

Figure 2: LightView Dashboard

Maven Meets LightView

Let’s start with Java EE 6 and Maven 3 integration. A JavaFX 2 application can be built like any other Java application. The NetBeans 7 IDE comes with good JavaFX 2 support, so JavaFX applications can be created, built, and deployed natively with NetBeans. NetBeans uses Ant behind the scenes for that purpose.

However, the majority of Java EE applications are continuously built and tested with the Maven build tool. The easiest way to create such a project is by executing mvn archetype:generate from the command line and accepting the suggested values (especially the maven-archetype-quickstart value). The mvn archetype:generate command creates a basic project of the jar type, as shown in Listing 1.

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.lightview</groupId>
    <artifactId>lightview</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>lightview</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>javafx</artifactId>
            <version>2.0</version>
            <systemPath>${fx.home}/rt/lib/jfxrt.jar</systemPath>
            <scope>system</scope>
        </dependency>
		<!-- remaining dependencies -->
    </dependencies>
        <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>


Listing 1: Maven 3 Project Object Model (POM) for Building and Running JavaFX 2 Applications

To make a JavaFX 2 application compilable, you also need the jfxrt.jar file in the class path. The reference can be added easily to your POM using the system scope. For this purpose, configure the location of fxrt.jar in the <systemPath> tag (see Listing 1). 

The jfxrt jar file is shipped with the JavaFX SDK. To keep the Maven pom.xml file environment independent, the actual path is specified in a property (fx.home) inside the javafx profile (see Listing 2).

<settings>
<profiles>
	<profile>
	         <id>javafx</id>
         <activation>
               <activeByDefault>false</activeByDefault>
         </activation>
            <properties>
	        <fx.home>[PATH_TO_JAVAFX]/javafx-sdk2.0.2-beta/</fx.home>
	    </properties>
</profiles>
</settings>


Listing 2: Maven Profile Settings

Alternatively, you can deploy the jfxrt.jar file to your local Maven repository using the following command:

mvn install:install-file -Dfile=jfxrt.jar -DgroupId=com.oracle -DartifactId=javafx -Dpackaging=jar -Dversion=2.0. 

After installing the jfxrt.jar file to your local repository, you can reference it as a usual dependency (see Listing 3) with compile scope.

<dependency>
	<groupId>com.oracle</groupId>
	<artifactId>javafx</artifactId>
	<version>2.0</version>
	<scope>compile</scope>
</dependency>


Listing 3: Referencing an Installed jfxrt.jar File

JavaFX Meets REST

LightFish exposes the monitoring snapshots periodically at a configurable interval. A Snapshot is accessible via a blocking GET request (for example, http://localhost:8080/lightfish/live) in the following format:

<snapshot>
	<usedHeapSize>86550272</usedHeapSize>
	<threadCount>94</threadCount>
	<peakThreadCount>94</peakThreadCount>
	<totalErrors>0</totalErrors>
	<currentThreadBusy>1</currentThreadBusy>
	<committedTX>21</committedTX>
	<rolledBackTX>0</rolledBackTX>
	<queuedConnections>0</queuedConnections>
	<pools>
		<jndiName>SamplePool</jndiName>
		<numconnfree>8</numconnfree>
		<waitqueuelength>0</waitqueuelength>
		…
	</pools>
</snapshot>


Listing 4: GlassFish Snapshot Data as XML

The connection is released on each “server push” that results in the arrival of a new Snapshot. Actively waiting for data arrival by releasing a blocked HTTP connection and pushing data to the client is known as “long polling,” and it is a common AJAX practice.

Similar to the vast majority of UI toolkits available today, JavaFX UIs are not thread-safe. The UI has to be accessed from the event queue and not by an application thread. The utility method javafx.application.Platform#runLater(java.lang.Runnable runnable) is intended to be invoked by application threads to update the UI with the results of asynchronous processing. We could use java.util.concurrent.ExecutorService together with Platform#runLater to update the UI asynchronously, but JavaFX 2 provides a more convenient alternative.

The class javafx.concurrent.Service is able to execute background threads and use their results to update the UI from the event queue. A synchronous method has to be wrapped with javafx.concurrent.Task for that purpose. Unlike javafx.concurrent.Task, a Service is reusable. Because we are going to poll the server to get a recent instance of the Snapshot class and recreate the Task instances, a Service is a natural choice. For one-shot use cases, such as sending non-idempotent requests with the HTTP-POST method, a standalone Task implementation would be sufficient, as shown in Listing 5.

package org.lightview.service;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.lightview.entity.Snapshot;

public class SnapshotProvider extends Service<Snapshot> {
    private final String liveDataURL;
	//accessors omitted
    &Override
    protected Task<Snapshot< createTask() {
        return new Task<Snapshot>() {
            &Override
            protected Snapshot call() throws Exception {
                SnapshotFetcher fetcher = new SnapshotFetcher(liveDataURL);
                return fetcher.getSnapshot();
            }
        };
    }
}


Listing 5: Executing Synchronous Services Asynchronously

Inside the method Task#call, the SnapshotFetcher is instantiated and the method getSnapshot is executed synchronously. The execution of SnapshotFetcher#getSnapshot() blocks and it waits until the next snapshot arrives (see Listing 6). 

import com.sun.jersey.api.client.Client;
import javax.ws.rs.core.MediaType;
import org.lightview.entity.Snapshot;

public class SnapshotFetcher {
    private final String url;
    private final Client client;
    //constructor omitted
    public Snapshot getSnapshot() {
         return client.resource(this.url).
			accept(MediaType.APPLICATION_XML).
			get(Snapshot.class);
    }
}


Listing 6: Fetching the Snapshot via HTTP

SnapshotFetcher is a pure Java Platform, Standard Edition (Java SE) class. The implementation of the GET HTTP request and the unmarshalling of the XML stream into the Snapshot entity are trivial. For HTTP communication, the REST client from the Jersey implementation of the Java API for RESTful Web Services (JAX-RS) specification was used. Jersey implements the HTTP communication with the back end as well as the conversion of the XML stream into the Snapshot instance. Jersey delegates the unmarshalling task to Java API for XML Binding (JAXB). To enable the Snapshot for serialization and deserialization, you only have to annotate it with the @XmlRootElement and @XmlAccessorType JAXB annotations (see Listing 7).

&XmlRootElement
&XmlAccessorType(XmlAccessType.FIELD)
public class Snapshot {
    private long id;
    private Date monitoringTime;
    private long usedHeapSize;
    public Snapshot() {   }

//other fields and accessors omitted
}


Listing 7: Snapshot Entity with JAXB Annotations

Alternatively, we could also get the Snapshot instance from the server with the java.net.URL#openStream method and deserialize the stream containing the XML representation of the Snapshot with the javax.xml.bind.JAXBContext class. With the plain java.net.URL and JAXBContext, you would get rid of an external library (two JAR files: jersey-core-1.9.1.jar and jersey-client-1.9.1.jar), but you would have to re-implement the existing Jersey client functionality with a few more lines of code. Since the whole HTTP communication is realized in a single class, SnapshotFetcher, the HTTP communication can be swapped later with negligible effort.

The Boundary Between REST and the UI

On every delivery of a new Snapshot instance, the SnapshotProvider, which is a subclass of javafx.concurrent.Service, has to be restarted and the views have to be updated with the monitoring data. The SnapshotProvider needs a connection URI to fetch the Snapshot. The class DashboardPresenter is the glue between the SnapshotProvider and the actual DashboardView, and it exposes the uri StringProperty to the SnapshotProvider, as shown in Listing 8.

public class DashboardPresenter implements DashboardPresenterBindings {
    private StringProperty uri;
    private ObservableList<Snapshot> snapshots;
    private ObservableMap<String, ConnectionPoolBindings> pools;
    SnapshotProvider service;
    private LongProperty usedHeapSizeInMB;
   //some property declarations omitted

    public DashboardPresenter() {
        this.snapshots = FXCollections.observableArrayList();
        this.pools = FXCollections.observableHashMap();
        this.uri = new SimpleStringProperty();
        this.usedHeapSizeInMB = new SimpleLongProperty();
        this.threadCount = new SimpleLongProperty();
	
        this.initializeListeners();
    }

    void initializeListeners() {
        this.uri.addListener(new ChangeListener<String>() {
            public void changed(ObservableValue<? extends String> observableValue, 
String s, String newUri) {
                restartService();
            }
        });
    }

    void restartService() {
        if (this.service != null && this.service.isRunning()) {
            this.service.cancel();
            this.service.reset();
        }
        this.startFetching();
    }


    public void setUri(String uri) {
        this.uri.setValue(uri);
    }

    public String getUri() {
        return this.uri.getValue();
    }

    public StringProperty getUriProperty() {
        return this.uri;
    }


    void startFetching() {
        this.service = new SnapshotProvider(getUri());
        service.start();
        service.valueProperty().addListener(
                new ChangeListener<Snapshot>() {

                    public void changed(ObservableValue<? extends Snapshot> 
observable, Snapshot old, Snapshot newValue) {
                        if (newValue != null) {
                            snapshots.add(newValue);
                            onSnapshotArrival(newValue);
                        }
                    }

                });
        registerRestarting();
    }

    void registerRestarting() {
        service.stateProperty().addListener(new ChangeListener<Worker.State>() {
            public void changed(ObservableValue<? extends Worker.State> observable, 
Worker.State oldState, Worker.State newState) {
                if (newState.equals(Worker.State.SUCCEEDED) || 
newState.equals(Worker.State.FAILED)) {
                    service.reset();
                    service.start();
                }
            }

        });
    }

    void onSnapshotArrival(Snapshot snapshot) {
        this.usedHeapSizeInMB.set(snapshot.getUsedHeapSizeInMB());
        this.threadCount.set(snapshot.getThreadCount());
	//some setters omitted
        this.updatePools(snapshot);
    }


    void updatePools(Snapshot snapshot) {
        List<ConnectionPool> connectionPools = snapshot.getPools();
        for (ConnectionPool connectionPool : connectionPools) {
            String jndiName = connectionPool.getJndiName();
            ConnectionPoolBindings bindings = ConnectionPoolBindings.from(connectionPool);
            ConnectionPoolBindings poolBindings = this.pools.get(jndiName);
            if(poolBindings != null){
                poolBindings.update(connectionPool);
            }else{
                this.pools.put(jndiName,bindings);
            }
        }
    }

  public ObservableList<Snapshot> getSnapshots() {
        return snapshots;
    }

    public ObservableMap<String,ConnectionPoolBindings> getPools() {
        return pools;
    }
//getters omitted…
}


Listing 8: DashboardPresenter—The Glue Between REST and the UI

The URI needed to get access to the real-time monitoring feed was implemented with a javafx.beans.property.SimpleStringProperty. JavaFX properties support synchronization between classes. A javafx.scene.control.TextField txtUri is directly bound to the URI property with a single line of code:

dashboardPresenter.getUriProperty().bind(txtUri.textProperty());

The text entered to the bound TextField instance is automatically synchronized with the StringProperty. The state of a JavaFX property is observable with a javafx.beans.value.ChangeListener. The method changed is invoked on any state change. These change notifications are used here to restart the Service upon each user input (see method initializeListeners in Listing 8) and change the URI.

DashboardPresenter listens to changes of the URI and reacts to them by reconfiguring and restarting the SnapshotProvider. DashboardPresenter subscribes itself in the method startFetching (see Listing 8) as a ChangeListener in the valueProperty() of the SnapshotProvider, as shown in Listing 9.

SnapshotProvider#valueProperty().addListener(new ChangeListener<Snapshot>() {
   public void changed(ObservableValue<? extends Snapshot> observable, Snapshot old, 
   Snapshot newValue) {}
}


Listing 9: ChangeListener Registration

The javafx.beans.property.ReadOnlyObjectProperty<T> valueProperty() is specified in the javafx.concurrent.Worker interface and realized in the javafx.concurrent.Service.

On each changed invocation (Listing 9) the DashboardPresenter forwards the Snapshot to the method onSnapshotArrival (Listing 10) and updates the value of the properties, effectively notifying and updating all interested listeners.

    void onSnapshotArrival(Snapshot snapshot) {
        this.usedHeapSizeInMB.set(snapshot.getUsedHeapSizeInMB());
        this.threadCount.set(snapshot.getThreadCount());
        this.peakThreadCount.set(snapshot.getPeakThreadCount());
	     //some updates omitted	
        this.updatePools(snapshot);
    }


Listing 10: SnapshotListener—The Interface Between the Presenter and the UI

Without any further intervention, after successful execution of the Task, the SnapshotProvider would change its internal State to Worker.State.SUCCEEDED and quit its execution. To receive periodical updates, a ChangeListener is used to listen to state changes and restart the service when it reaches the ending states Worker.State.SUCCEEDED or Worker.State.FAILED, as shown in Listing 11.

void registerRestarting() {
SnapshotProvider#stateProperty().addListener(new ChangeListener<Worker.State>(){
   public void changed(ObservableValue<? extends Worker.State> observable, 
Worker.State oldState, Worker.State newState) {
                if(newState.equals(Worker.State.SUCCEEDED) || 
newState.equals(Worker.State.FAILED)){
                    service.reset();
                    service.start();
                }
            }
        });
}


Listing 11: Automatic Service Restart

Unlike javafx.concurrent.Task, a javafx.concurrent.Service implementation can be reused multiple times. By reacting to the stateProperty (Listing 11) and restarting the Service when it reaches its end states, a javafx.concurrent.Service becomes a perfect vehicle for the implementation of a long-polling communication style. On each execution of the HTTP GET request, the Task is completed, the payload is delivered, and the Service transitions itself to the SUCCEEDED state. A ChangeListener registered to the stateProperty resets and starts the service again.

Binding for Decoupling

DashboardPresenter exposes the Snapshot entity to the views as a set of bindable attributes defined in the DashboardPresenterBindings interface (see Listing 12).

public interface DashboardPresenterBindings {
    StringProperty getUriProperty();
    ReadOnlyLongProperty getId();
    ReadOnlyLongProperty getUsedHeapSizeInMB();
    ReadOnlyLongProperty getThreadCount();
    ReadOnlyIntegerProperty getPeakThreadCount();
    ReadOnlyIntegerProperty getBusyThreads();
    ReadOnlyIntegerProperty getQueuedConnections();
    ReadOnlyIntegerProperty getCommitCount();
    ReadOnlyIntegerProperty getRollbackCount();
    ReadOnlyIntegerProperty getTotalErrors();
    ObservableMap<String, ConnectionPoolBindings> getPools();
    ObservableList<Snapshot> getSnapshots();
}


Listing 12: DashboardPresenterBindings—The Interface Between the View and the Presenter

Properties starting with the ReadOnly prefix in Listing 12 can be used only for refreshing the view. Only the getUriProperty() method exposes a writable StringProperty bound to a TextField. All read-only properties contain the data of the most recent Snapshot entity. From the perspective of a view, a JavaFX property is an up-to-date bindable attribute, and so it is a conveniently usable data type. Each DashboardPresenterBindings property is synchronized with the view through a single line of code:        

this.heapView.value().bind(this.dashboardPresenter.getUsedHeapSizeInMB());

GlassFish JDBC connection pools can be created and installed dynamically and they are, therefore, exposed as a dynamic and bindable ObservableMap. The key to the map is a typical string, and the value is the class ConnectionPoolBindings (see Listing 13).

public class ConnectionPoolBindings {
    private StringProperty jndiName;
    private IntegerProperty numconnfree;
    private IntegerProperty waitqueuelength;
    private IntegerProperty numpotentialconnleak;
    private IntegerProperty numconnused;

    private ConnectionPoolBindings() {
        this.jndiName = new SimpleStringProperty();
	//…
    }
    public ReadOnlyStringProperty getJndiName() {
        return jndiName;
    }
    public ReadOnlyIntegerProperty getNumconnfree() {
        return numconnfree;
    }
    public void setJndiName(String jndiName) {
        this.jndiName.set(jndiName);
    }
    public void setNumconnfree(int numconnfree) {
        this.numconnfree.set(numconnfree);
    }
  //some accessors omitted
    public static ConnectionPoolBindings from(ConnectionPool connectionPool) {
        ConnectionPoolBindings poolBindings = new ConnectionPoolBindings();
        poolBindings.update(connectionPool);
        return poolBindings;
    }
    void update(ConnectionPool connectionPool) {
        this.setJndiName(connectionPool.getJndiName());
        this.setNumconnfree(connectionPool.getNumconnfree());
        this.setNumpotentialconnleak(connectionPool.getNumpotentialconnleak());
        this.setNumconnused(connectionPool.getNumconnused());
        this.setWaitqueuelength(connectionPool.getWaitqueuelength());
    }
}


Listing 13: ConnectionPoolBindings Adapter

The runtime statistics of a ConnectionPool are exposed directly as the ConnectionPoolBindings class. The ConnectionPoolBinding class can be considered to be an adapter: It converts a ConnectionPool in a set of JavaFX properties. The state of the properties can be changed only internally. An external user of the class is able to get only read-only access to the data exposed by the getters. An instance of the ConnectionPoolBinding is created with the static method from and refreshed with the method update. ConnectionPoolBinding is independent of the DashboardPresenter and can also be mocked out as a class. There is no need for further decoupling by introducing an additional interface.

The method ObservableList<Snapshot> getSnapshots() from the DashboardPresenterBindings publishes all Snapshot instances gathered so far and makes them conveniently available to the JavaFX javafx.scene.control.TableView control.

Clear Separation of Responsibilities

The fine-grained binding model allows clear separation of the view, presentation, and business logic. The SnapshotFetcher is independent of any JavaFX library. Its single responsibility is communicating with the back end and converting an InputStream into a Snapshot instance. The SnapshotProvider integrates the SnapshotFetcher with the JavaFX threading model and implements the periodic fetching of Snapshot instances in the background. Both classes supersede the classic Business Delegate pattern and they are the proxy to Java EE back-end services.

With the conversion of the Snapshot instance into fine-grained properties, the SnapshotPresenter is the glue between the back-end services and the view logic. The DashboardPresenter entirely decouples the view from the implementation of the datasource and makes the realization of the view more convenient. To receive monitoring data, a view only has to bind its view components to the published SnapshotPresenterBindings interface.

In part 2 of this series, we will discuss the structure of the dashboard from the view perspective as well as the direct integration of the JavaServer Faces 2 UI with WebView.

See Also

About the Author

Consultant and author Adam Bien is an Expert Group member for the Java EE 6 and 7, EJB 3.X, JAX-RS, CDI, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with Servlets/EJB 1.0 in several large-scale projects, and he is now an architect and developer for Java SE and Java EE projects. He has edited several books about JavaFX, J2EE, and Java EE, and he is the author of Real World Java EE Patterns—Rethinking Best Practices and Real World Java EE Night Hacks—Dissecting the Business Tier. Adam is also a Java Champion and JavaOne 2009 Rock Star.