4

Let's create the simplest Hello World app using JavaFX 8 with FXML:

Files

src/application/Main.java:

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main extends Application {
    @Override
    public void start(Stage stage) {
        try {
            System.out.println("Main.start()");
            FXMLLoader fxml_loader = new FXMLLoader();
            fxml_loader.setLocation(getClass().getResource("Sample.fxml"));
            System.out.println("FXML resource URL = " + getClass().getResource("Sample.fxml"));
            Parent root = fxml_loader.load(); 
            Scene scene = new Scene(root, 300, 200);
            stage.setScene(scene);
            stage.setTitle("JFX HW");
            stage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

src/application/Sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.StackPane?>

<StackPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
   <children>
      <Label text="Hello World" />
   </children>
</StackPane>

Workflow

Using your favorite IDE, compile all into a bin folder:

$ find bin
bin
bin/application
bin/application/Main.class
bin/application/Sample.fxml

then create a jar:

$ javapackager -createjar -appclass application.Main -srcdir bin -outdir compiled -outfile jfxhw -v -manifestAttrs "Application-Name=JFXHW,Permissions=sandbox,Codebase=*"

One can verify here that the jar file works properly with java -jar jfxhw.jar.

Let's sign it:

$ jarsigner compiled/jfxhw.jar MYALIAS

Deploy:

$ javapackager -deploy -appclass application.Main -srcdir compiled -outdir deployed -outfile index -width 300 -height 200 -name JFXHW -v

$ find deployed
deployed/
deployed/jfxhw.jar
deployed/index.jnlp
deployed/index.html

The Outcome

The command:

javaws index.jnlp

fails with (you need to enable the console to see this):

Main.start()
FXML resource URL = null
java.lang.IllegalStateException: Location is not set.
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2438)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
    at application.Main.start(Main.java:18)
    :

What am I doing wrong?

sebnukem
  • 8,143
  • 6
  • 38
  • 48
  • Yes, the deploy folder contains the jar, jnlp and html files. – sebnukem Jul 20 '15 at 20:05
  • Yes, it is self-signed. Not sure I understand the second question. I have tried serve the index.html from Apache and white-list the URL if it's what you mean? – sebnukem Jul 20 '15 at 20:22

1 Answers1

5

Problem

The application needs sufficient permissions to call the fxml file. You haven't set any permissions in your deploy step, so in the resulting jnlp file the security part is omitted.

There is an already known bug and it is scheduled to be fixed in Java 9. This bug is related to your problem, but not the root cause.

https://bugs.openjdk.java.net/browse/JDK-8088866

for the inconsistence of jarsign and javapackager -signjar.

My attempts to get rid of it:

  1. Doing the whole thing like you, ended with the same jnlp that isn't runnable
  2. Switched on JConsole and Tracing to get more detail, but again fail
  3. Try the javapackager -signjar command and not the jarsigner like you, but again fail
  4. Searched the Bug-Database at bugs.openjdk.java.net, that showed me that there is a problem of verifying the signed jar that was created by javapackager -signjar
  5. Searched for more complications about applet, jnlp and signing and found this: https://weblogs.java.net/blog/cayhorstmann/archive/2014/01/16/still-using-applets-sign-them-or-else
  6. Try to create the deploy with -allpermissions added to deploy step of javapackager. This works with restrictions on self-signed certificates!
  7. Try to create a javafx ant task with an embeded "normal" signjar task. This works! (BTW: This is how Netbeans it does if you check "Request unristricted access (Enable signing)" under project properties.

Solution with javapackager

Add the -allpermissions Parameter to your command line:

javapackager -deploy -allpermissions -appclass application.Main -srcdir compiled -outdir deployed -outfile index -width 300 -height 200 -name JFXHW -v

But there is a little problem: Your manifest.mf will not contain the empty Class-Path tag like this is added by the ant script. The Java Security Prompt will pop up and you are not able to run the application until you added the certificate to your trusted root certificates store in your os.

Solution with ant script

First I changed the permissions in the deploy task to set it elevated:

<fx:permissions elevated="true" cacheCertificates="true"/>

Second I created the following build.xml file and put it in the root of your project. Your jnlp will be created in the folder dist. And you have to set the storepass for your keystore. And the correct alias and keystore file name. If your are not on windows, the "env" probably will not work. Set it hard to your jdk.

Simply call it with "ant default" in the root directory of your project.

build.xml

<?xml version="1.0" encoding="UTF-8"?>

<project name="App" default="default" basedir="."
         xmlns:fx="javafx:com.sun.javafx.tools.ant">

  <property environment="env"/>
  <property name="build.src.dir" value="src"/>
  <!-- <property name="build.resources.dir" value="${build.src.dir}/resources"/> -->
  <property name="build.classes.dir" value="classes"/>
  <property name="build.dist.dir" value="dist"/>


  <target name="default" depends="clean,compile">

    <!-- defines the classpath -->
    <path id="cp">
      <filelist>
        <file name="${env.JAVA_HOME}/lib/ant-javafx.jar"/>
        <file name="${basedir}" />
      </filelist>
    </path>

    <!-- defines the task with a reference to classpath -->
    <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
             uri="javafx:com.sun.javafx.tools.ant"
             classpathref="cp"/>


    <fx:application id="appId"
                    name="jfxHw"
                    mainClass="application.Main"/>


    <!-- Defines the resources needed by the application -->
    <fx:resources id="appRes">
      <fx:fileset dir="${build.dist.dir}" includes="jfxHw.jar"/>
      <!-- <fx:fileset dir="${build.dist.dir}" includes="bsigned_jfxHw.jar"/> -->
    </fx:resources>

    <!-- Create a jar file -->
    <fx:jar destfile="${build.dist.dir}/jfxHw.jar">
      <fx:application refid="appId"/>
      <fx:resources refid="appRes"/>
      <fileset dir="${build.classes.dir}"/>
      <fileset dir="${build.src.dir}">
        <exclude name="**/*.java"/>
      </fileset>
      <manifest>
        <attribute name="Permission" value="all-permissions"/>
      </manifest>
    </fx:jar>

    <signjar alias="myAlias" keystore="myKeystore.jks"
             storepass="******"
             preservelastmodified="true">
      <path>
        <fileset dir="${build.dist.dir}" includes="**/*.jar" />
      </path>
    </signjar>


    <fx:deploy width="600" height="400" outdir="${basedir}//${build.dist.dir}/"
               outfile="jfxhw" verbose="true">
      <fx:info title="jfxHw"/>
      <fx:application refid="appId"/>
      <fx:resources refid="appRes"/>
      <fx:permissions elevated="true" cacheCertificates="true"/>
    </fx:deploy>

  </target>

  <!-- Removes the folders of previous runs -->
  <target name="clean">
    <mkdir dir="${build.classes.dir}"/>
    <mkdir dir="${build.dist.dir}"/>

    <delete>
      <fileset dir="${build.classes.dir}" includes="**/*"/>
      <fileset dir="${build.dist.dir}" includes="**/*"/>
    </delete>
  </target>

  <!-- Compiles the sources -->
  <target name="compile" depends="clean">
    <javac includeantruntime="false"
           srcdir="${build.src.dir}"
           destdir="${build.classes.dir}"
           fork="yes"
           executable="${env.JAVA_HOME}/bin/javac"
           source="1.8"
           debug="on">
    </javac>
  </target>

</project>

MANIFEST.MF

This is created after all is done. Note that I set the manifest permissions to all-permissios for debugging purposes. You can safely set it to sandbox.

Manifest-Version: 1.0
JavaFX-Version: 8.0
Permission: all-permissions
Class-Path: 
Created-By: JavaFX Packager
Main-Class: application.Main

Name: application/Sample.fxml
SHA-256-Digest: eT8+7c2XeVhURexj5X9Y1xAP2H8YIMcieeySOmgOPZw=

Name: application/Main.class
SHA-256-Digest: Md+alMOmoQpslZIgLwbmPFAI8axSKBVvReXZFgoKJ6A=

jfxhw.jnlp

Watch the security part:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://javafx.com" href="jfxhw.jnlp">
  <information>
    <title>jfxHw</title>
    <vendor>Unknown vendor</vendor>
    <description>Sample JavaFX 2.0 application.</description>
    <offline-allowed/>
  </information>
  <resources>
    <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se"/>
    <jar href="jfxHw.jar" size="3089" download="eager" />
  </resources>
<security>
  <all-permissions/>
</security>
  <jfx:javafx-desc  width="600" height="400" main-class="application.Main"  name="jfxHw" />
  <update check="background"/>
</jnlp>
aw-think
  • 4,723
  • 2
  • 21
  • 42