The Enterprise Side of JavaFX: Part Three

by Adam Bien

Learn to build and deploy JavaFX applications in three different ways.

Published August 2012

Downloads:
JavaFX

In Part 1 of this series, we discussed the service and model layer of a JavaFX GlassFish monitoring application called LightView and the conversion of REST services into a bindable set of properties. Then in Part 2, we discussed the implementation of the LightView UI dashboard with JavaFX 2.

JavaFX 2 supports multiple strategies for the way JavaFX applications are deployed. JavaFX applications can be deployed as embedded browser applications, as installable Java Web Start applications, or as standalone Java applications.

This article focuses on using Maven 3 to build and deploy the LightView application in all the available deployment modes. We will also explore how to sign and deploy LightView with a Java EE 6 application.

Is Monolithic Deployment Best Practice?

LightView uses the HTTP (REST) protocol to communicate with the back-end server. For the realization of back-end communication, an external library—the Jersey client—is used. LightView connects with the back end (LightFish) at startup time, so it is not suitable to lazy-load the Jersey dependencies for optimization purposes. Furthermore, multiple JAR files are hard to handle for standalone applications; you have to set up the class path correctly and keep all the moving parts consistent. The most convenient way to deploy Java (and JavaFX) applications is simply by starting them with java -jar my-killer-app.jar and deploying a single file that contains all the dependencies.

Instead of using the standard Maven deployment mechanism, all dependencies except testing libraries (Mockito, JUnit, and Hamcrest), are going to be extracted into the ./target/classes directory (see Listing 1).

   <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>unpack-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <excludeScope>system</excludeScope>
                            <excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
                            <outputDirectory>${project.build.directory}/classes</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
   </plugin>

Listing 1. Extracting All Dependencies with the maven-dependency-plugin

Immediately after the extraction, the class files are packaged with the javafxpackager, which is shipped with the JavaFX 2 SDK, using the exec-maven-plugin (see Listing 2).

     <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <id>unpack-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>${fx.home}/bin/javafxpackager</executable>
                    <arguments>
                        <argument>-createjar</argument>
                        <argument>-nocss2bin</argument>
                        <argument>-appclass</argument>
                        <argument>org.lightview.App</argument>
                        <argument>-srcdir</argument>
                        <argument>${project.build.directory}/classes</argument>
                        <argument>-outdir</argument>
                        <argument>../lightfish/src/main/webapp</argument>
                        <argument>-outfile</argument>
                        <argument>${project.name}-app</argument>
                    </arguments>
                </configuration>
     </plugin>

Listing 2. Executing the javafxpackager from Maven

The exec-maven-plugin shown in Listing 2 does the following tasks:

  • Disassembles the following command:

    ${JAVAFX_HOME}/bin/javafxpackager -createjar -nocss2bin -appclass 
    org.lightview.App -srcdir ./target/classes -outdir ../lightfish/src/main/webapp 
    -outfile lightview-app. Javafxpackager 
    
  • Takes the classes from the ./target/classes folder
  • Creates an executable JAR file that contains the main class, org.lightview.App
  • Stores the result directly in the LightFish project in the src/main/webapp directory

Since the LightView cascading style sheet (CSS) is simplistic, we don't have to convert the file to binary format for optimization purposes. The option -nocss2bin prevents the javafxpackager from converting the CSS into binary format.

On machines with a properly installed JavaFX SDK, you could launch LightView using the following command:

java -jar ../lightfish/src/main/webapp/lightview-app.jar 

However, if the JavaFX SDK was installed by extracting the archive, you will have to point to the installation with a system property:

java -Djavafx.runtime.path=${JAVAFX_HOME}/rt -jar ../lightfish/src/main/webapp/lightview-app.jar

This accomplishes our first goal: We can start LightView from the command line or just by double-clicking an executable JAR file.

What Happens Behind the Scenes

The javafxpackager creates a usual executable JAR file with an unusual MANIFEST.MF file:

JavaFX-Version: 2.0
JavaFX-Application-Class: org.lightview.App
Created-By: JavaFX Packager
Main-Class: com/javafx/main/Main

The JAR file is executable, but instead of pointing to the org.lightview.App directly, it makes com/javafx/main/Main executable. The wrapper uses the JavaFX-Application-Class parameter to execute org.lightview.App indirectly.

To find an executable method, the com.javafx.main.Main wrapper searches for the JavaFX libraries and tries either to execute an internal launchApplication method or directly call the usual public static void main method. Because JavaFX is, in the end, just Java, LightView is directly executable as well. In order to start a JavaFX application directly, the class path to the JavaFX SDK has to be set manually.

Two additional classes, com.javafx.main.Main (the wrapper) and com.javafx.main.NoJavaFXFallback, are added to the created JAR file by the javafxpackager. In case the JavaFX runtime or Java SE 5 (or later) is not installed, the class Main is used to inspect the environment and set the class path. The other added class, NoJavaFXFallback, is an Applet used as a fallback to direct the user to download and install the runtime.

LightView Needs Them All

The LightView application polls the application server to simulate server push or the so-called "long polling" communication style. LightView executes a GET request, which is blocked by the server, and waits for monitoring data. For the implementation of the asynchronous execution of the GET request, the class javafx.concurrent.Service is used. The javafx.concurrent.Service is restarted on each successful server push with the methods Service#reset and Service#start. Unfortunately, the method Service#reset requires the modifyThreadGroup permission, which has to be granted explicitly. Execution without <all-permissions/> causes the error shown in Listing 3.

java.lang.ExceptionInInitializerError
  at org.lightview.presenter.DashboardPresenter.startFetching(DashboardPresenter.java:103)
  at org.lightview.presenter.DashboardPresenter.restartService(DashboardPresenter.java:85)
  at org.lightview.presenter.DashboardPresenter$1.changed(DashboardPresenter.java:75)
  at org.lightview.presenter.DashboardPresenter$1.changed(DashboardPresenter.java:73)
  ...
      Caused by: java.security.AccessControlException: access denied        ("java.lang.RuntimePermission" "modifyThreadGroup")
  at java.security.AccessControlContext.checkPermission(Unknown Source)
  at java.security.AccessController.checkPermission(Unknown Source)
  at java.lang.SecurityManager.checkPermission(Unknown Source)
  at sun.plugin2.applet.SecurityManagerHelper.checkAccessHelper(Unknown Source)
  at sun.plugin2.applet.FXAppletSecurityManager.checkAccess(Unknown Source)
  at java.lang.ThreadGroup.checkAccess(Unknown Source)
  at java.lang.ThreadGroup.checkParentAccess(Unknown Source)
  at java.lang.ThreadGroup.<init>(Unknown Source)
  at java.lang.ThreadGroup.<init>(Unknown Source)
  at javafx.concurrent.Service.<clinit>(Unknown Source)
  ... 

Listing 3. Stack Trace Caused by Lack of Permissions

To assign the permissions, we will have to sign the JAR file and extend the Java Network Launching Protocol (JNLP) descriptor with the all-permissions tag:

<jnlp spec="1.0" xmlns:jfx="http://javafx.com" href="lightview-web.jnlp">
...
   <security>
        <all-permissions/>
    </security>
...
</jnlp>

To accomplish signing the JAR file, you first have to create a key with the keystore tool. For demo purposes, we will use the following command to create a self-signed certificate:

keytool -selfcert -dname "cn=Adam Bien, ou=Fun, o=adam-bien.com, c=DE" -alias 
lightview -keypass [SECRET] -keystore [PATH]/keystore/samples -storepass [SECRET] 

Maven comes with a convenient maven-jarsigner-plugin (see Listing 4), which signs the JAR file on each build (mvn -Pjavafx install).

      <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jarsigner-plugin</artifactId>
                <version>1.2</version>
                <executions>
                    <execution>
                        <id>sign</id>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <keystore>[PATH]/keystore/samples</keystore>
                    <alias>lightview</alias>
                    <storepass>[SECRET]</storepass>
                    <keypass>[SECRET]</keypass>
                    <archive>../lightfish/src/main/webapp/${project.name}-app.jar</archive>
                </configuration>
      </plugin>

Listing 4. Configuration of the Maven JAR Signer Plug-in

We add the maven-jarsigner-plugin in the same "package" phase as the javafxpackager with the exec-maven-plugin. The order of plug-in declaration also mandates the order of execution. Therefore, the maven-jarsigner-plugin is going to be executed before the archive is created with the exec-maven-plugin.

You can verify the signing process by looking at the MANIFEST.MF file. You should include additional SHA1-Digest entries after the signing process:

Name: org/lightview/presenter/ConnectionPoolBindings.class
SHA1-Digest: SFl+kYUKM+wtDB0VmE9gMyetU4A=

Beginning with JDK 1.7, keytool also supports JAR file verification. The command keytool -printcert -jarfile ../lightfish/src/main/webapp/lightview-app.jar generates the output shown in Listing 5 for signed JAR files:

Owner: CN=Adam Bien, OU=Fun, O=adam-bien.com, C=DE
Issuer: CN=Adam Bien, OU=Fun, O=adam-bien.com, C=DE
Serial number: 4f322dcf
Valid from: Wed Feb 08 09:09:51 CET 2012 until: Thu Feb 07 09:09:51 CET 2013
Certificate fingerprints:
     MD5:  1D:80:8C:4D:A8:C0:27:DA:E8:17:84:35:FD:63:F9:AA
     SHA1: F2:C1:0B:91:B6:E0:33:F0:FC:F6:30:F1:ED:13:94:21:15:B1:15:AA
     SHA256: D2:A7:4B:DA:4B:2C:10:43:30:98:74:4E:68:EC:AF:18:A6:73:31:E9:2D:CD:70:58:
6F:A5:2D:4E:B1:A1:C6:E9
     Signature algorithm name: SHA1withDSA
     Version: 3

Listing 5. Output from JAR File Verification

An attempt to validate an unsigned JAR file results in the following message:

Not a signed jar file

With the configuration of two additional Maven plug-ins (see the skeleton in Listing 6), we can automatically build a signed JavaFX application.

<project>
    <groupId>org.lightview</groupId>
    <artifactId>lightview</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <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>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.9.1</version>
        </dependency>
         <!--test dependencies omitted-->
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
               ...
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
             ...
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jarsigner-plugin</artifactId>
             ...
            </plugin>
        </plugins>
    </build>
</project>

Listing 6. pom.xml Skeleton for Building and Signing JavaFX Applications

Java Web Start and Embedded Execution

A signed JAR file is a mandatory requirement for granting <all-permissions/> for Java Web Start applications. After accomplishing the signing, we need to create the necessary JNLP deployment descriptor and integrate it with the LightFish back-end service.

LightFish is a standalone application that gathers GlassFish monitoring data and exposes it via XML in Comet-like style. LightView connects via HTTP to LightFish and is notified, according to a configurable schedule, about the availability of new monitoring data.

LightView is not usable without LightFish. In fact, the best user experience would be to launch LightView directly from the LightFish admin page as a Java Web Start application. To accomplish this, LightView is packaged with LightFish and served directly from the WAR file.

The JNLP deployment descriptors, the HTML files, and the signed JAR file are copied into the src/main/webapp directory. Both the JNLP deployment descriptor and the HTML files rarely change. The static descriptors are created manually and are not updated by Maven. On the other hand, the signed lightview-app.jar file is updated upon each LightView build.

We add an additional link to the LightFish index.xhtml JavaServer Faces (JSF) page pointing to lightview.html:

<a href="./lightview.html" target="_blank" id="lightview">LightView</a> 

When this link is clicked, the LightView application is supposed to verify the environment, install a declared Java and JavaFX version (if necessary), embed LightView in a browser, and then start to gather the GlassFish monitoring data.

The heavy lifting is accomplished by the Java Deployment Toolkit, which is a set of useful JavaScript functions that are able to check and install a Java environment, as well as generate the needed HTML content on the fly (see Listing 7).

<html>
<head>
    <script type="text/javascript" src="http://java.com/js/dtjava.js"></script>
        <script>
        function launchApplication(jnlpfile) {
            dtjava.launch(            {
                        url : jnlpfile
                    },
                    {
                        javafx : '2.1+'
                    },
                         {}
            );
            return false;
        }
        function deployIt() {
            dtjava.embed(
                    {   id: "lightview-web",
                        url: "lightview-web.jnlp",
                        width: 800,
                        height: 600,
                        placeholder: "lightviewExecution"
                    },
                    { javafx: "2.1+"
                    },
                    {}
            );
        }
        dtjava.addOnloadCallback(deployIt);
    </script>
</head>
<body>
<div id="lightviewExecution"></div>
<a href='lightview-web.jnlp' onclick="return launchApplication('lightview-web.jnlp');">
Launch and install LightView</a>
</body>
</html>

Listing 7. Using the Deployment Toolkit to Embed a JavaFX Application

At the load time of the lightview.html page, the JavaScript method deployIt is called. The environment is checked twice and the user is prompted to install the proper Java version (see Figure 1).

Figure 1: Executing Lightview When Java Is Not Installed

Figure 1. Executing LightView When Java Is Not Installed

After clicking the link, the user is prompted to install Java directly from the Oracle Website (see Listing 2).

Figure 2: Java Installation Web Page

Figure 2. Java Installation Web Page

The availability of the required JavaFX version on the target machine is also checked and the user is prompted to install JavaFX if the requirements are not met (see Figure 3).

Figure 3: Executing LightView When JavaFX Is Not Installed

Figure 3. Executing LightView When JavaFX Is Not Installed

If the expected Java and JavaFX versions are already available on the target machine, LightView is downloaded and executed. The location of the embedded LightView application is specified by the <div id> tag that contains the lightviewExecution identifier shown in Listing 7.

In addition to the embedded execution, an external link to a Java Web Start installation is provided (see Figure 4).

Figure 4: Embedded LightView Execution

Figure 4. Embedded LightView Execution

Clicking the "Launch and install LightView" link invokes the JavaScript function launchApplication. The call is enriched by specifying the JavaFX execution requirements and passing the name of the JNLP deployment descriptor to the dtjava.launch function.

For the embedded execution and the Java Web Start execution, the same JNLP deployment descriptor file is used (see Listing 8).

<jnlp spec="1.0" xmlns:jfx="http://javafx.com" href="lightview-web.jnlp">
    <information>
        <title>LightView Real Time Monitoring</title>
        <vendor>adam-bien.com</vendor>
        <homepage href="http://adam-bien.com"/>
        <description>Real Time GlassFish Monitoring Dashboard</description>
        <offline-allowed/>
        <shortcut online="false" install="true">
            <desktop/>
            <menu submenu="LightView GlassFish Monitoring"/>
        </shortcut>
    </information>
    <resources os="Windows">
        <jfx:javafx-runtime version="2.1+"
     href="http://javadl.sun.com/webapps/download/GetFile/javafx-latest/windows-i586/javafx2.jnlp"/>
    </resources>
    <resources>
        <j2se version="1.6+" href="http://bit.ly/xyGWeN"/>
        <jar href="lightview-app.jar" download="eager"/>
    </resources>
    <security>
        <all-permissions/>
    </security>
    <applet-desc width="800" height="600" main-class="com.javafx.main.NoJavaFXFallback" name="LightView"/>
    <jfx:javafx-desc width="800" height="600" main-class="org.lightview.App" name="LightView"/>
    <update check="always"/>
</jnlp>

Listing 8. JNLP Deployment Descriptor

The JNLP deployment descriptor points to the signed lightview-app.jar, sets the expected security permissions, suggests the creation of a desktop shortcut, and provides additional metadata. The skeleton for the HTML and JNLP files was generated with the javafxpackager tool:

javafxpackager -deploy -nocss2bin -appclass org.lightview.App -srcdir 
./target/classes -outdir ./target/webstart -outfile lightview-web -width 800 
-height 600 -name "LightView" -title "LightView Real Time Monitoring" -vendor 
adam-bien.com.

The Marriage of LightFish and LightView

The back-end Java EE services and the front-end JavaFX application can be considered as a single application. In order to build the WAR file, you will have to execute the Maven build of LightView and then proceed with the LightFish build. A Maven Project Object Model (POM) project can automate this task and combine both builds. The pom.xml file in the multilight project points to the lightview and lightfish projects and causes a build in the right order (see Listing 9).

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.lightfish</groupId>
  <artifactId>multilight</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>multi-light</name>
    <modules>
        <module>../lightview</module>
        <module>../lightfish</module>
    </modules>
</project>

Listing 9. A Multiproject Build via multilight

Detecting the Execution Environment

When it is executed as a standalone application, LightView cannot know where the server lives and it tries to connect using the URI http://localhost:8080/. With Java Web Start, however, the server URI starts with the String returned by the javafx.application.HostServices#getDocumentBase method. Because LightView was installed from within the LightFish WAR file, the base URI is the same. Fortunately, the execution mode can be easily detected with getDocumentBase() method. For standalone applications, the URI starts with file: and for Java Web Start applications, it starts with http or https (see Listing 10).

public class App extends Application {
    @Override
    public void start(Stage primaryStage) {
        String serverURI = getServerURI();
        System.out.println("Base URI: " + serverURI);
        DashboardPresenter dashboardPresenter = new DashboardPresenter(serverURI);
        new Dashboard(primaryStage,dashboardPresenter);
    }
    String getServerURI(){
        HostServices hostServices = getHostServices();
        if(runsInBrowser(hostServices)){
            return extractHostWithPort(hostServices.getDocumentBase());
        }
        return null;
    }
    boolean runsInBrowser(HostServices hostServices) {
         return (!hostServices.getDocumentBase().startsWith("file:"));
    }
    String extractHostWithPort(String uri){
        int fromIndex = "https://".length();
        int index = uri.indexOf("/",fromIndex);
        return uri.substring(0,index);
    }
    public static void main(String[] args) throws MalformedURLException {
        launch(args);
    }
}

Listing 10. Detecting the Execution Mode with HostServices#getDocumentBase

If the base URI starts with file:, LightView was started as a standalone application, so we have to fall back to the http://localhost:8080 default; otherwise, the base URI is directly reused.

Adjusting the Web Content

LightView can be installed, downloaded, and executed directly from the LightFish JSF admin page, but that page is also directly displayed to the user within LightView. Without any intervention, the installation link would appear again inside the WebView component. This would be confusing to the user, and it would also take up precious dashboard space.

With the WebEngine class, you can also control the content that is loaded into the WebView component. After the admin page is completely loaded, the link to the LightView installation is removed by the method Browser#postProcess (see Listing 11).

import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class Browser extends Collapsible {
	
    private StringProperty uri = new SimpleStringProperty();

    private WebEngine engine;
    private WebView webView;
	...

    Node view() {
        if(webView == null)
            initialize();
        return webView;
    }

    private void initialize() {
		...
        this.registerListeners();
    }

    private void registerListeners() {
		...
       registerWorkDoneListener(engine);
    }

    void registerWorkDoneListener(final WebEngine engine) {
        final Worker<Void> loadWorker = engine.getLoadWorker();
        loadWorker.progressProperty().addListener(new ChangeListener<Number>() {
            public void changed(ObservableValue<? extends Number> observableValue, 
          Number old, Number current) {
                if(current.doubleValue() == 1.0){
                    postProcess(engine.getDocument());
                }
            }
        });
    }

    void postProcess(Document lightFishAdminPage) {
        Element lightview = lightFishAdminPage.getElementById("lightview");
        if(lightview == null){
            return;
        }
        lightview.setAttribute("style","display: none");
    }
}

Listing 11. Making the Installation Link Disappear

An anonymous ChangeListener is registered to the Worker#progressProperty in the method Browser#registerWorkDoneListener (see Listing 11). The listener waits until the progress is 1.0 (or until the page is fully loaded) to invoke the method postProcess. The link to the LightView installation comes with a unique ID:

<a href="./lightview.html" target="_blank" id="lightview">LightView</a>

In the postProcess method, an org.w3c.dom.Element is obtained from the Document#getElementById("lightview") method. If such an element exists, the style attribute is set to display: none, which makes the link element invisible.

JavaFX...In the End, It Is "Just" Java

JavaFX is particularly interesting for the development of enterprise applications, and JavaFX applications can be deployed as embedded browser applications, Java Web Start applications, or standalone applications.

The availability of multiple execution modes allows JavaFX applications to be distributed and installed through different channels. Because JavaFX is "just" an additional Java library, all of the established build, test, and deployment infrastructure can be reused. You can develop JavaFX applications using any integrated development environment (IDE) you like. And best of all, you can use a single language in a project, from the Java EE back end to the JavaFX front end.

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 and JavaOne 2011 Rock Star.

Join the Conversation

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