JavaFX Integration Strategies

by Adam Bien
Published November 2013

With lambdas and support for asynchronous communication, JavaFX introduces new integration possibilities for back-end services.

You will rarely find isolated applications in the enterprise. An enterprise desktop application renders and manipulates the data of one or more back-end services exposed by an application server. In the old Swing and J2EE days, the communication was unidirectional and synchronous. JavaFX and Java EE 6 and 7 introduced a variety of new synchronous, asynchronous, push, and pull integration strategies. This article focuses on the integration of Java EE services with JavaFX applications.

JavaFX Is Java

JavaFX is Java. Thus, the best practices you use for Swing applications, you can apply to JavaFX. The integration of back-end services is both protocol- and technology-agnostic.

Services involve various configurations such as IP addresses, ports, and property files. And the methods of the APIs often throw protocol-specific exceptions such as java.rmi.RemoteException and, therefore, pollute the presentation logic with irrelevant detail. A thin wrapper around the proprietary service encapsulates the implementation details and exposes a more meaningful interface. It's a classic Gang of Four (GoF) Adapter pattern.

Revival of the Business Delegate

J2EE clients heavily relied on Remote Method Invocation over Internet Inter-ORB Protocol (RMI-IIOP) communication and, later, on Java API for XML-based RPC (JAX-RPC) and Java API for XML Web Services (JAX-WS) with back-end services. Both APIs use heavily checked exceptions and are tied to the particular technology. The Business Delegate pattern was necessary to decouple the presentation logic from the protocol:

"Use a Business Delegate to reduce coupling between presentation-tier clients and business services. The Business Delegate hides the underlying implementation details of the business service, such as lookup and access details of the EJB architecture."

A Business Delegate was often extended with a factory that created real proxies in default case and mock objects in test environments. With modern mock libraries, such as Mockito, a Business Delegate can be directly mocked out. A Business Delegate in a JavaFX and Java EE context can be implemented as an adapter Plain Old Java Object (POJO) that encapsulates the implementation details and exposes a convenient interface to JavaFX.

Figure 1

Figure 1

First Request, Then Response

Sending a blocking request to an application server and then waiting for the data to arrive is the simplest possible integration with the back end. The Business Delegate becomes a service that communicates with the back end, as shown in Listing 1:




import javax.json.JsonObject;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class MethodMonitoring {

    private Client client;

    public void init() {
        this.client = ClientBuilder.newClient();
    }
    public MethodsStatistics getMethodStatistics(String application, String ejbName) {
        final String uri = getUri();
        WebTarget target = this.client.target(uri);
        Response response = target.
                resolveTemplate("application", application).
                resolveTemplate("ejb", ejbName).
                request(MediaType.APPLICATION_JSON).get(Response.class);
        if (response.getStatus() == 204) {
            return null;
        }
        return new MethodsStatistics(response.readEntity(JsonObject.class));
    }
 }

Listing 1

The MethodMonitoring class is easy to implement and test and can be integrated with a presenter. Because the method getMethodStatistics can potentially block for an unlimited amount of time, a synchronous invocation from a UI listener method would make the UI unresponsive.

Asynchronous Integration

Fortunately, the JAX-RS 2.0 API also supports an asynchronous, callback-based communication model. Instead of blocking, the method getMethodStatistics initiates the request and registers a callback (see Listing 2).



import javax.json.JsonObject;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import java.util.function.Consumer;

public class MethodMonitoring {
    private Client client;

    @PostConstruct
    public void init() {
        this.client = ClientBuilder.newClient();
    }

    public void getMethodStatistics(Consumer<MethodsStatistics> consumer, Consumer<Throwable> error, 
    String application, String ejbName) {
        final String uri = getUri();
        WebTarget target = this.client.target(uri);
        target.
                resolveTemplate("application", application).
                resolveTemplate("ejb", ejbName).
                request(MediaType.APPLICATION_JSON).async().get(new InvocationCallback<JsonObject>() {
            @Override
            public void completed(JsonObject jsonObject) {
                consumer.accept(new MethodsStatistics(jsonObject));
            }

            @Override
            public void failed(Throwable throwable) {
                error.accept(throwable);
            }
        });
    }
}

Listing 2

The callback transforms the incoming JsonObject into a domain object and passes it to the implementation of a java.util.function.Consumer. The implementation of the Business Delegate is still independent from the JavaFX API and uses the Java 8 java.util.function.Consumer as a callback. With Java 7, any custom interface or class could be used as a callback. However, with Java 8, the realization of the JavaFX presenter can be drastically simplified, as shown in Listing 3.



...
      this.methodMonitoring.getMethodStatistics(s -> onArrival(s), 
        t -> System.err.println(t), this.monitoredApplication, ejb);
...
    void onArrival(MethodsStatistics statistics) {
        Platform.runLater(() -> {
            this.methodStatistics.clear();
            this.methodStatistics.addAll(statistics.all());
        }
        );
    }

Listing 3

The java.util.function.Consumer can be implemented as a lambda expression, which greatly reduces the amount of code. JavaFX is a single-threaded UI toolkit, so it cannot be accessed by multiple threads asynchronously.

A lambda implementation of the java.lang.Runnable interface is passed to the Platform.runLater method and added to the event queue for later execution. According to the Javadoc, "Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater."

The Platform#runLater method is not suitable for the execution of long-running tasks; rather, it is just for the updates of JavaFX UI components from an asynchronous thread.

Tasks for Real Work

Platform.runLater is not meant for the implementation of "heavy lifting"; rather, it is for fast updates of the JavaFX nodes. Asynchronous invocation of long-running methods requires the creation of threads and is natively supported by JavaFX with the javafx.concurrent.Task class. A Task realizes the Worker and EventTarget interfaces, inherits from the java.util.concurrent.FutureTask class, and can be considered a bridge between Java threading and the JavaFX event mechanism. Task can be either directly used by a Thread as an ordinary Runnable or passed to an ExecutorService as a Callable. In either case, synchronous Java EE APIs without the ability of asynchronous execution, for example, IIOP, could be wrapped with a Business Delegate first:



public class SnapshotFetcher{ 
...
    public Snapshot getSnapshot() {
        return fetchFromServerSynchronously();
    }
...

Listing 4

In the next step, the blocking Business Delegate method is wrapped with a Task and is now able to be executed asynchronously.



Task<Snapshot> task = new Task<Snapshot>() {
            @Override
            protected Snapshot call() throws Exception {
                SnapshotFetcher fetcher = new SnapshotFetcher();
                return fetcher.getSnapshot();
        };

Listing 5

A Task is a Runnable and a Future, so it could be executed either directly by a Thread or submitted to an Executor. JavaFX comes with a javafx.concurrent.Service class that seamlessly integrates threading with the UI using bindable properties. A Service is actually a Task factory:




import javafx.concurrent.Service;
import javafx.concurrent.Task;
import org.lightview.model.Snapshot;

public class SnapshotProvider extends Service<Snapshot> {
  @Override
    protected Task<Snapshot> createTask() {
        return new Task<Snapshot>() {
            @Override
            protected Snapshot call() throws Exception {
                SnapshotFetcher fetcher = new SnapshotFetcher();
                return fetcher.getSnapshot();
        };
        };
    }
}

Listing 6

The state of Service as well as the Task execution results become available as bindable JavaFX properties:




        this.service = new SnapshotProvider();
        service.start();
        service.valueProperty().addListener(
                (observable,old,newValue) ->{
                //process new value
                    });

Listing 7

The Task class is a convenient vehicle for asynchronous integration of synchronous legacy resources or just for the execution of long-running processes at the client side.

Blocking for Asynchronicity

Comet and long polling are ugly hacks for the simulation of push HTTP-based communication with browsers. HTTP is a request-response protocol, so a response can be sent only as an answer to a request. Pushing data to a browser over HTTP without an initial request is, therefore, impossible. Long-polling communication style is easy to implement: the browser initiates a connection that is blocked by the server. The server uses the blocking connection to send data to the browser, which immediately closes the connection. The browser processes the data and reinitiates a subsequent blocking connection with the server. If there is nothing to say, the server sends a 204 request to the browser.

JavaFX applications are deployed in the enterprise as standalone Java applications and, therefore, not limited to HTTP communication. However, often REST endpoints are available for HTML5 clients as well and could be directly reused by JavaFX applications. REST and JSON become the new, least common denominator for communication with HTML5 clients, Java applications, and even low-level devices.

JavaFX applications can directly participate in long polling and can be notified in the same way as HTML5 clients. The only difference between synchronous communication and long polling is the repetitive initiation of a blocking call. The recurring polling can be directly implemented with javafx.concurrent.Service. Regardless of whether the execution fails or succeeds, the service will be reset and then restarted:



javafx.concurrent.Service service = ...;
    void registerRestarting() {
        service.stateProperty().addListener((observable,oldState,newState) -> {
                if (newState.equals(Worker.State.SUCCEEDED) ||
                        newState.equals(Worker.State.FAILED)) {
                    service.reset();
                    service.start();
            }
        });
    }

Listing 8

Push Integration

Push communication is a request-response style of communication without the request part; the application server can push out data at any time. Java Message Service (JMS), WebSockets, and memory grids come with a fire-and-forget style notification mechanism and can be easily integrated with JavaFX.

JSR 356 implements the WebSocket protocol, is part of Java EE 7, and comes with a Java client API. The WebSocket specification introduces a bidirectional binary protocol and is perfectly suitable for the integration of UI clients. WebSocket messages can have a binary or text nature and are received with an Endpoint subclass, as shown in Listing 9:



import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;

public class SnapshotEndpoint extends Endpoint {

    private SimpleObjectProperty<Snapshot> snapshot;
    private final Unmarshaller unmarshaller;
    private JAXBContext jaxb;

    public SnapshotEndpoint() {
        try {
            this.jaxb = JAXBContext.newInstance(Snapshot.class);
            this.unmarshaller = jaxb.createUnmarshaller();

        } catch (JAXBException e) {}
        this.snapshot = new SimpleObjectProperty<>();
    }

    @Override
    public void onOpen(Session session, EndpointConfig ec) {
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String message) {
                final Snapshot current = deserialize(message);
                Platform.runLater(() ->
                        snapshot.set(current));
            }
        });
    }
    Snapshot deserialize(String message) {
        try {
            return (Snapshot) unmarshaller.unmarshal(new StringReader(message));
        } catch (JAXBException e) {}
    }
}

Listing 9

The class SnapshotEndpoint receives a string message and converts it using the Java Architecture for XML Binding (JAXB) API. The Snapshot domain object is an annotated POJO:



@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Snapshot {
 //...arbitrary fields
    private long id;
}

Listing 10

The JSR 356 API supports extensions, so the serialization and deserialization could be factored out into a dedicated class. Also, we are not limited to JAXB; we can use any available representation of the object, such as JSON or serialization. The SnapshotEndpoint is executed on the client side by a dedicated WebSocket thread, so the message cannot be used to update only the UI. With the Platform.runLater method, the message is properly passed from the WebSocket to the JavaFX thread.

An Endpoint is responsible only for the actual communication. In addition, the WebSocket container requires initial configuration and initialization for what is implemented in a dedicated class:



public class SnapshotSocketListener {
    private SnapshotEndpoint endpoint;
    private Session session;

    public SnapshotSocketListener() {
        this.endpoint = new SnapshotEndpoint();
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        ClientEndpointConfig config = ClientEndpointConfig.Builder.create().build();
        String uri = "ws://localhost:8080/lightfish/snapshots/";
        try {
            session = container.
            connectToServer(this.endpoint, config, URI.create(uri));
        } catch (DeploymentException | IOException e) {
            throw new IllegalStateException("Cannot connect to WebSocket: ", e);
        }
    }

    public ReadOnlyObjectProperty<Snapshot> snapshotProperty() {
        return endpoint.snapshotProperty();
    }

    @PreDestroy
    public void disconnect() {
        try {
            session.close();
        } catch (IOException e) {
        }
    }
}

Listing 11

So far, we have barely used JavaFX integration capabilities; instead, we have focused on various integration styles. JavaFX properties, however, make synchronous and asynchronous integration particularly interesting.

Integration the JavaFX Way

A javafx.beans.property.ObjectProperty wraps an Object instance and is bindable. An interested client can register as a listener or directly bind to the property and receive notifications if the wrapped instance gets replaced. (See the "Binding for Narrow Interfaces" section of the Java Magazine article "JavaFX Data Binding for Enterprise Applications.") Regardless of the communication protocol and synchronicity, a response carries a domain object that has to be displayed by the UI. With an ObjectProperty, the UI can just directly bind to the value and get automatically notified upon data arrival. The presenter directly binds to the ObjectProperty, which makes additional management methods superfluous:



        this.snapshotSocketListener.snapshotProperty().
        addListener((o, oldValue, newValue) -> {
            snapshots.add(newValue);
            onSnapshotArrival(newValue);
        });

Listing 12

A bindable ObjectProperty significantly removes the clutter and makes the interface remarkably "narrow." Binding can also be applied for the outbound communication. A presenter changes the state of a domain object or model, which causes the notification of the service (Business Delegate). The outbound communication does not require any synchronization with the UI threads. Asynchronous operations can even be directly executed from the UI thread, and long-running operations can either be wrapped with a javafx.concurrent.Service or asynchronously executed within the Business Delegate. However, not all UI operations change the state of a domain object. Simple user actions such as a "save" or "refresh" can be directly translated to Business Delegate method invocations.

One Step Further for Responsiveness and Simplicity

A JavaFX UI is event driven and asynchronous. Also, Java EE 7 APIs—such as JAX-RS 2.0, JMS, or WebSockets—come with asynchronous capabilities. Using JavaFX together with asynchronous Java EE 7 APIs significantly simplifies the code. All Business Delegate operations can be performed asynchronously without blocking the UI or even the Business Delegate itself. The interaction pattern is independent of the communication protocol and can be applied consistently for all asynchronous Java EE 7 APIs.

A request is sent to the server in a "fire-and-forget" fashion. Instead of waiting for the response, a callback method is registered for response processing. The callback receives the data, populates a domain object, and replaces the current domain object using the ObjectProperty#set within the Platform.runLater method. Changes to the domain object are propagated to the presenter, which multicasts the changes to any interesting views.

A fully asynchronous communication greatly reduces the amount of necessary code. Using a fire-and-forget approach in both directions with data binding eliminates the need for data synchronization between a temporary model and the master state on the server side. All actions are directly passed to the Business Delegate, and all responses coming from the application server directly update the UI.

Also the user experience (UX) can be greatly improved with fully asynchronous interaction; regardless of how expensive a server side interaction is, the UI will never block.

Conclusion

At first glance, the integration of JavaFX with back-end services is very similar to Swing. Platform#runLater is equivalent to javax.swing.SwingUtilities#invokeLater and the intentions of javafx.concurrent.Service are similar to javax.swing.SwingWorker.

Modern Java EE 7 APIs, JavaFX data binding (see "JavaFX Data Binding for Enterprise Applications"), and the Inversion of Control capabilities with FXML and Scene Builder (see "Integrating JavaFX Scene Builder into Enterprise Applications") allow us to greatly simplify the presentation logic and introduce consistent approaches for implementing multiview desktop applications.

With Java EE 6 and 7 back ends, you can continue with the asynchronous interaction style on the server side as well.

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, and JPA 2.X JSRs. He has worked with Java technology since JDK 1.0 and with Servlets/EJB 1.0 and 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, Top Java Ambassador 2012, and JavaOne 2009, 2011, and 2012 Rock Star. Adam occasionally organizes Java EE workshops at Munich's Airport (http://airhacks.com).

Join the Conversation

Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!