Using the gradle-jnlp-plugin – part 2

This collection of articles describe the gradle-jnlp-plugin from Tobias Schulte, hosted at Github.

The plugin produces a webstart distribution for a JavaSE application as required by the webstart specification. It creates the jnlp file and compresses/signs the jar files. The directory created by the plugin may then be uploaded to your static web server or embedded into your war file.

Describe jar signing process

Following options are available within the jnlp and describe the certificate that will sign the jar files.

signJarParams A map of key/value pairs that describe the certificate that signs the jar files.
Optional. If omitted, or if the map is empty, no jar file becomes signed.
The plugin uses the Ant signjar task. Any of the options described at signjar documentation may be used as entry in this map. The relevant attributes are:
keystore Path of file that contains the keystore with the certificate.
Required.
storepass Password to decrypt the keystore file.
Required.
alias Alias that identifies the certificate within the keystore file.
Required.
keypass Password to decrypt the certificate within the keystore file.
Optional. If omitted, defaults to the value passed to storepass.
tsaurl URL of webservice that provides timestamp authority.
Optional. If omitted, the jar file signature will expire together with the certificate.
signJarAddedManifestEntries A map of key/value pairs to be added as properties to each manifest in jars distributed by the application.
Optional. Defaults to [ 'Codebase': '*', 'Permissions' : 'all-permissions', 'Application-Name': "${project.name}" ].
signJarFilteredMetaInfFiles Optional. Defaults to '(?:SIG-.*|.*[.](?:DSA|SF|RSA)|INDEX.LIST)'
signJarRemovedManifestEntries A regular expression that matches names of properties that are to be removed from each manifest in jars distributed by the application.
Optional. Defaults to '(?:Trusted-Only|Trusted-Library)'
signJarRemovedNamedManifestEntries A regular expression that matches names of properties that are to be removed from each manifest in jars distributed by the application.
Optional. Defaults to '(?:.*-Digest)'.

The default values for signJarAddedManifestEntries and signJarRemovedManifestEntries are the most permissive (less secure) configuration available at the webstart specification. It allows the jar files to access user’s system resources (like reading and writing local files). I allows reusing the jar files on other domains and other applications.

These two permissions should be fine for most common webstart applications. Recent webstart environment requires jar files, even for dependencies, to declare required permissions to validate successfully while loading application. I would only change their values if sandboxing your application is really a requirement or if someone must not reuse any of your jar files. If so, see Preventing RIAs from Being Repurposed.

Further, I experienced some rare situations where invalid manifest entries from thard party jar files were not accepted by the target webstart JVM. I had to remove these entries using signJarRemovedManifestEntries.

The default values for signJarFilteredMetaInfFiles and signJarRemovedNamedManifestEntries remove any existing signature from third party jar files. Such signatures (or self-signatures) may not be recognized by the target webstart JVM, preventing your application from launching. This default options will conservatively replace any existing signature by a signature from your own certificate.

This should be fine if you own a valid certificate, unless licensing issues would prevent you from redistributing third party jar files using your own certificate.

You may provide the signing options within one line:

jnlp {
   signJarParams = [keystore: '../keystore.ks', alias: 'myalias', storepass: 'mystorepass']
}

Or as individual attributes:

jnlp {
   signJarParams. keystore = '../keystore.ks'
   signJarParams. alias= 'myalias'
   signJarParams. storepass = 'mystorepass'
}

Describe branding

A user will expect to recognize your application on several branding artifacts that identify your application. Typically, such artifacts are application name, splash screen and icons that appear on the OS’s taskbar/desktop/main menu.

Such artifacts are defined within the jnlp file. Instead of providing a large set of options for this purpose, the gradle-jnlp-plugin maintainer preferred to let you write own XML snippet to be placed within the jnlp file. The XML elements are documented at Structure of the JNLP File – Information element.

Such XML snippet is described as groovy script as documented at Processing XML – Creating XML. For simplicity, I present an example that will cover most relevant branding artifacts.

jnlp {
    withXml {
       information {
          /* Application name that is shown while downloading the application,
           * on webstart security dialogs, on OS's desktop icon and 
           * OS's main menu entry. */
          title project.name

          /* Company name and homepage that is shown while downloading the 
           * application, on webstart security dialogs. */
          vendor project.group ?: project.name
          homepage (href: 'www.example.com.br')

          /* Several text versions that explain the application purpose. */
          description (kind: 'one-line', 'One line description.')
          description (kind: 'short', 'More than one line description.')
          description (kind: 'tool-tip', 'Tooltip description.')

          /* Application icon, of several sizes. The target JVM will choose the
           * one that best fits for each situation. This example assumes 
           * 3 versions of the same icone with sizes: 16x16, 32x32 and 
           * 48x48 pixels. The icon is described by a href relative to the 
           * JNLP file. If the jnlp is hosted at www.example.com/app/launch.jnlp,
           * the first icon will be downloaded from 
           * www.example.com/app/images/main-16.png. 
           * All icons should be png files. */
          icon (href: 'images/main-16.png')
          icon (href: 'images/main-32.png')
          icon (href: 'images/main-48.png')

          /* Image for application splash screen, presented while downloading
           * and verifying the jar files. */
          icon (href: 'images/splash.png', kind: 'splash')

          shortcut {
             /* Add shortcut with icon to OS's desktop. */
             desktop()
             /* Add shortcut with icon to submenu inside the OS's main menu. */
             menu (submenu: 'submenu-name')
          }
       }
       /* Through the jar files declare permissions, these permissions
        * must be declared again in the JNLP file. */
       security {
           'all-permissions'()
       }
   }
}

Using the gradle-jnlp-plugin – part 1

This collection of articles describe the gradle-jnlp-plugin from Tobias Schulte, hosted at Github.

The plugin produces a webstart distribution for a JavaSE application as required by the webstart specification. It creates the jnlp file and compresses/signs the jar files. The directory created by the plugin may then be uploaded to your static web server or embedded into your war file.

Applying the plug-in

The gradle-jnlp-plugin is hosted in JCenter and is registered in the Gradle Plug-in Portal. It is also recommended to apply the application plugin.

buildscript {
   repositories {
      jcenter()
   }
   dependencies {
      classpath 'de.gliderpilot.gradle.jnlp:gradle-jnlp-plugin:+'
   }
}
apply plugin: 'application'
apply plugin: 'de.gliderpilot.jnlp'

Relevant tasks

The gradle-jnlp-plugin provides three relevant tasks.

createWebstartDir Bundles the project as a webstart distribution. Creates the jnlp file and compresses/signs the jar files. The directory created by this tasks may be uploaded to your static web server or embedded into your war file.
webstartDistTar Bundles the webstart distribution within a .tar file.
webstartDistZip Bundles the webstart distribution within a .zip file.

Customization

A jnlp extension is available for the project in order to describe the webstart distribution. The default values are reasonable most webstart requirement.

The gradle-jnlp-plugin assumes the main class declared for the application plugin. For a production ready application, you need to declare certificate to sign the jar files.

mainClassName = 'package.name.to.MainClass'
jnlp {
   signJarParams.keystore = 'keystore.ks'
   signJarParams.alias = 'myalias'
   signJarParams.storepass = 'mystorepass'
}

Describe the distribution

Following options are available within the jnlp extension with the purpose to describe the webstart distribution.

href File name and file extension of the jnlp file created.
Optional. Defaults to “launch.jnlp”
codebase Base URL where the application is hosted. All relative URLs specified in href attributes in the jnlp file are using this URL as a base.
Optional. When the application is distributed within a war file using some kind of jnlp servlet, the codebase should be “$$codebase”.
spec The webstart specification required by the distribution.
Optional. Defaults to “7.0”.
mainClassName Fully qualified name of the class containing the main method.
Optional. Defaults to project.mainClassName, as defined by the application plugin.

The available options are exactly the attribute names for the jnlp element within the jnlp file, as described by the jnlp syntax specification.

I recommend changing only, if needed, the href option. The users may prefer reading something like application_name.jnlp instead of launch.jnlp. All other options work well with their default value and there is no relevant reason to change them.

Example:

jnlp {
   href 'myapplication.jnlp'
   codebase 'http://gliderpilot.de'
   spec '7.0'
}

Describe the JVM

Following options are available within the jnlp extension with the purpose to describe minimal JVM requirements.

j2seParams A map of key/value pairs that describe the JVM required to execute the application.
Optional. Defaults to [version: current-JVM-version]
Possible key names are:
version Ordered list of version ranges to use. For example, “1.7+” means that your application requise Java 7 or higher.
Optional.
href The URL denoting the supplier of this version of java, and where it may be downloaded from.
Optional.
java-vm-args Indicates an additional set of standard and non-standard virtual machine arguments that the application would prefer the JNLP Client to use when launching Java.
Optional.
initial-heap-size Indicates the initial size of the Java heap.
Optional.
max-heap-size Indicates the maximum size of the Java heap.
Optional.

The available keys are exactly the attribute names for the java (or j2se) element within the jnlp file, as described by the jnlp syntax specification.

You may provide the JVM options within one line:

jnlp {
   j2seParams = [version: '7.0+', 'max-heap-size': '256m']
}

Or as individual attributes, which I prefer for better readability:

jnlp {
   j2seParams.version = '7.0+'
   j2seParams.'max-heap-size' = '256m'
}

I recommend changing only, if your application crashes without OutOfMemoryExceptions, the j2seParams.initial-heap-size/j2seParams.'max-heap-size' options. As far as I understand this plugin, if you set any j2seParams option, you also need to set the j2seParams.version manually.

Further customization options will be explained on my next article about the gradle-jnlp-plugin.

Choosing a Gradle Java Webstart/JNLP Plugin

While developing a JavaFX application distributed with Webstart, I had to choose a Gradle plugin in order to package the application. Unfortunately, Gradle does not support Webstart out of the box. After experiencing several available plugins, I decided for the gradle-jnlp-plugin from Tobias Schulte, hosted at Github.

His gradle-jnlp-plugin creates a webstart distribution for a JavaSE application as required by the webstart specification. It creates the jnlp file and compresses/signs the jar files. The directory created by the plugin may then be uploaded to your static web server or embedded into your war file.

I discovered that the gradle-jnlp-plugin is stable and functional. While simple, it supports nearly the complete webstart specification. The default configuration revealed itself reasonable for most requirements. And the maintainer has been reacting to recent issues. That gave me confidence that the plugin won’t become abandoned as happened to many other similar plugins.

Signing jnlp is not supported. Fortunately, this is rarely required on recent webstart specification. And there is no support for war distribution containing the jnlp servlet. I suppose that the maintainer does not recommend this approach, since the recent webstart specification made it simpler to launch the application without the jnlp servlet.

Unfortunately, besides some examples, there is nearly no documentation. Having a good understanding about the webstart specification and after reading the plugin’s source code, I discovered that the gradle-jnlp-plugin is quite intuitive. Therefore, I decided to write another post to help other people which are new to gradle or webstart.

On the whole, I am grateful to Tobias Shulte for spending his time making this useful plugin freely available to the community. I was relevant help while developing my JavaFX webstart application.