Netbeans, JNI, Webstart and Apache

Although NetBeans has a Webstart module, it isn't really up to snuff yet, as I detailed in a previous post. Specifically, it doesn't support JNI code, and it can't sign the JAR files, which is needed if you have any JNI code to deploy. However, it's possible to add the necessary goop to the Netbeans build.xml file to automate this, with the help of a couple of short scripts. In addition, we can compress the JAR files using pack200 - this can give really impressive results, for example it compresses the Matisse swing-layout-1.0.2.jar file down from 155Kb down to just 19Kb! The final flourish is to get the build process to generate the Apache configuration files needed to serve Webstart files correctly. The examples below are all Win32-based as that's the environment my application works in, but they are trivially convertable to *nix versions.

The first step is to create a self-signed certificate if you don't have a proper one, the steps for doing this are described in the keytool documentation - you need to first create the certificate using the -genkey option, then sign it with the -selfcert option.

Next, we need to add some extra properties into the nbproject/project.properties.file file to describe the key file and the directories we will use for the webstart build, you should edit these to reflect your setup:

# Properties needed for signing JAR files
webstart.src.dir=webstart_src
webstart.dist.dir=webstart_dist
key.alias=bleaklow.com
keystore.location=d:/shared/bleaklow.keys
keystore.password=password

We also need a couple of helper scripts - you should put these in the nbprojects directory:

signpack.bat

@echo off
REM compress and sign a JAR file, remove the original
REM args are <JAR> <keystore> <password> <alias>
pack200 --repack --no-keep-file-order --strip-debug %1
jarsigner -keystore %2 -storepass %3 %1 %4
pack200 --effort=9 %1.pack.gz %1
del %1

This script will pack the supplied JAR file using the pack200 utility, and will then sign it with the supplied key. The --repack dance is necessary because packing a file with pack200 may change the order of the contents of the file and if it was signed this would break the signing, so we need to pack it, unpack it, then sign it and finally repack it again.

genvar.bat

@echo off
REM generate a type mapping file for Apache
REM arguments are <JAR>
echo URI: packed/%1.pack.gz>%1.var
echo Content-Type: application/x-java-archive>>%1.var
echo Content-Encoding: pack200-gzip>>%1.var
echo.>>%1.var
echo URI: unpacked/%1>>%1.var
echo Content-Type: application/x-java-archive>>%1.var

This script generates the type mapping file needed to get Apache to serve either the normal unpacked versions or the packed versions of the files we are going to deploy. The layout of the generated file needs to be exact, down to each whitespace - if you want to understand this in detail see the pack200 deployment guide and the Apache documentation for content negotiation, but my advice is just to accept that the magic files created by the script work ;-)

Now we need to create the Apache .htaccess file and the Webstart descriptor (JNLP) file. Create a new directory - webstart_src and create the following two files in it:

.htaccess

AddType application/x-java-jnlp-file .jnlp
AddType application/x-java-archive .jar
AddHandler application/x-type-map .var
Options +MultiViews
<Files *.pack.gz>
    AddEncoding pack200-gzip .jar
    RemoveEncoding .gz
</Files>

This assumes that your webserver httpd.conf is set up to allow the .htaccess file to function, if not see the Apache docs for how to do this. This file sets up the MIME types for the Webstart files we are going to serve, and establishes the content negotiation mechanism needed to serve the packed versions.

OziGeocacheUK.jnlp

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jnlp PUBLIC "-//Sun Microsystems, Inc.//DTD JNLP 1.5//EN"
  "http://www.netbeans.org/jnlp/DTD/jnlp.dtd">
<jnlp spec="1.0+"
 codebase="http://oziapi-java.sourceforge.net/OziGeocacheUK"
 href="OziGeocacheUK.jnlp">
  <information>
    <title>OziGeocacheUK</title>
    <vendor>bleaklow.com</vendor>
    <homepage href="http://oziapi-java.sourceforge.net/OziGeocacheUK"/>
    <description>GeocacheUK access for OziExplorer</description>
    <icon href="logo64.gif" kind="splash"/>
    <icon href="logo32.gif"/>
    <offline-allowed/>
    <shortcut>
      <desktop/>
    </shortcut>
  </information>
  <security>
     <all-permissions/>
  </security>
  <resources os="Windows">
    <j2se version="1.5+" href="http://java.sun.com/products/autodl/j2se"/>
    <jar href="OziGeocacheUK.jar"/>
    <jar href="OziAPI.jar"/>
    <jar href="swing-layout-1.0.1.jar"/>
    <nativelib href="OziAPINative.jar"/>
  </resources>
  <application-desc main-class="com.bleaklow.ozigeocacheuk.Main"/>
</jnlp>

You should name this file appropriately for your project - in my case the Netbeans project is called OziGeocacheUK. Obviously this file will need modifying to suit your particular project - the format of JNLP files is documented here, but I'll point out the most important bits:

  • offline_allowed
    Java Web Start will also check to see if an update is available. However, if the application is already downloaded the check will timeout after a few seconds, in which case the cached application will be launched instead.
  • security/all-permissions
    The application will have full access to the client machine and local network. If an application requests full access, then all JAR files must be signed.

In my directory I also have two icon files, icon32.gif and icon64.gif which are 32x32 and 64x64 pixel icon files that I use with my application. If you do have these files, they must be GIF format, Webstart doesn't support PNG.

The final step is to add an Ant target to build a Webstart-deployable file tree to your build.xml file, the following should be fairly self-explanatory:

<!-- Create a webstart tree -->
<target name="build-webstart" depends="init,compile"
  description="Build a webstart version">
    <!-- 1. Create a fresh webstart directory -->
    <delete dir="${webstart.dist.dir}"/>
    <mkdir dir="${webstart.dist.dir}/unpacked"/>
    <mkdir dir="${webstart.dist.dir}/packed"/>

    <!-- 2. Copy the source files -->
    <copy todir="${webstart.dist.dir}">
        <fileset dir="${webstart.src.dir}"/>
    </copy>

    <!-- 3. Copy in all the java JAR files -->
    <copy todir="${webstart.dist.dir}/unpacked" flatten="true">
        <fileset dir="${dist.dir}" includes="**/*.jar"/>
    </copy>

    <!-- 4. Create a JAR file from the DLLs -->
    <jar destfile="${webstart.dist.dir}/unpacked/OziAPINative.jar"
      basedir="${dist.dir}" includes="*.dll"/>

    <!-- 5. Create packed and signed versions of the JAR files -->
    <copy todir="${webstart.dist.dir}/packed">
        <fileset dir="${webstart.dist.dir}/unpacked" includes="*.jar"/>
    </copy>
    <apply executable="nbproject/signpack.bat" type="file"
      resolveexecutable="true" failonerror="true" ignoremissing="false">
        <fileset dir="${webstart.dist.dir}/packed" includes="*.jar"/>
        <srcfile/>
        <arg value="${keystore.location}"/>
        <arg value="${keystore.password}"/>
        <arg value="${key.alias}"/>
    </apply>

    <!-- 6. Sign all the unpacked JAR files -->
    <signjar
        alias="${key.alias}"
        keystore="${keystore.location}"
        storepass="${keystore.password}" >
        <fileset dir="${webstart.dist.dir}" includes="**/*.jar" />
    </signjar>

    <!-- 7. Create the Apache type mapping files -->
    <apply executable="nbproject/genvar.bat" type="file" relative="true"
      dir="${webstart.dist.dir}" dest="${webstart.dist.dir}"
      resolveexecutable="true" failonerror="true" ignoremissing="false">
        <fileset dir="${webstart.dist.dir}/unpacked" includes="*.jar"/>
        <flattenmapper/>
    </apply>
    <fixcrlf srcdir="${webstart.dist.dir}" eol="lf" eof="remove"
       includes="*.var"/>
</target>

Running this target in Netbeans produces the following directory tree under the webstart_dist subdirectory of your Netbeans project:

.htaccess
logo32.gif
logo64.gif
OziGeocacheUK.jnlp
packed\OziAPI.jar.pack.gz
packed\OziAPINative.jar.pack.gz
packed\OziGeocacheUK.jar.pack.gz
packed\swing-layout-1.0.1.jar.pack.gz
unpacked\OziAPI.jar
unpacked\OziAPINative.jar
unpacked\OziGeocacheUK.jar
unpacked\swing-layout-1.0.1.jar

This tree can then be directly deployed to an Apache webserver as it contains everything needed to serve your application. I'll cover how you can get Netbeans to automatically deploy the application in a subsequent post.

Categories : Java, Web, Tech


Re: Netbeans, JNI, Webstart and Apache

Hello Alan,

Thank you for a great article!

I was looking for a way to feed precompiled Pack200/GZ
to WebStart without any special servlets etc.

Yeah, a strange thing that WebStart can't take Pack200/GZ files directly.
It needs a server and HTTP tricks. It's weird why Sun did not do this!

I played with your code and made simple version for my needs.
I tested it on my SourceForge.net project site. It works fine there.

.htaccess
=====================
AddType application/x-java-jnlp-file .jnlp
AddType application/x-java-archive .jar

<Files *.pack.gz>
AddEncoding pack200-gzip .jar
RemoveEncoding .gz
</Files>
=====================

Pack200/GZIPped JAR:
=====================
myfile.jar.pack.gz
=====================

Part of JNLP:
=====================
<resources>
<jar href="myfile.jar.pack.gz" />
</resources>
=====================

Re: Netbeans, JNI, Webstart and Apache

Alan, I can't seem to get it to work on the Apache we are using. My first question would be how are the packed and unpacked directories resolved? In the jnlp there is no indication of them. Thakns Fabio

Re: Netbeans, JNI, Webstart and Apache

Alan -- forget it, it's the .var file that takes care of it. My bad.

Re: Netbeans, JNI, Webstart and Apache

Alan -- I was able to configure Apache to serve the pack.gz files, however my 1.6 javaws fails when verifying the signature:
java.lang.SecurityException: SHA1 digest error for com/xxx/yyy/core/meta/MetaInfo.class
at sun.security.util.ManifestEntryVerifier.verify(Unknown Source)
at java.util.jar.JarVerifier.processEntry(Unknown Source)
at java.util.jar.JarVerifier.update(Unknown Source)
at java.util.jar.JarVerifier$VerifierStream.read(Unknown Source)
at com.sun.deploy.cache.CacheEntry.writeManifest(Unknown Source)
at com.sun.deploy.cache.CacheEntry.writeFileToDisk(Unknown Source)
at com.sun.deploy.cache.Cache.downloadResourceToCache(Unknown Source)
at com.sun.deploy.net.DownloadEngine.actionDownload(Unknown Source)
at com.sun.deploy.net.DownloadEngine.getCacheEntry(Unknown Source)
at com.sun.deploy.net.DownloadEngine.getResourceCacheEntry(Unknown Source)
at com.sun.deploy.net.DownloadEngine.getResourceCacheEntry(Unknown Source)
at com.sun.deploy.net.DownloadEngine.getResource(Unknown Source)
at com.sun.javaws.LaunchDownload.downloadJarFiles(Unknown Source)
at com.sun.javaws.LaunchDownload.downloadEagerorAll(Unknown Source)
at com.sun.javaws.Launcher.downloadResources(Unknown Source)
at com.sun.javaws.Launcher.prepareLaunchFile(Unknown Source)
at com.sun.javaws.Launcher.prepareToLaunch(Unknown Source)
at com.sun.javaws.Launcher.launch(Unknown Source)
at com.sun.javaws.Main.launchApp(Unknown Source)
at com.sun.javaws.Main.continueInSecureThread(Unknown Source)
at com.sun.javaws.Main$1.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Please note that I'm using your unmodified bat scripts to pack and sign my jars. Any help is appreciated, Thanks Fabio

Re: Netbeans, JNI, Webstart and Apache

Try cleaning out the files and rebuilding the signed JAR files from scratch using 1.6 - there were bugs in this area in 1.5

Re: Netbeans, JNI, Webstart and Apache

The SHA1 digest error is related to the following bug:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6351684

I.e. I don't think just rebuilding using 1.6 will fix it