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.
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
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
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