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.

Datanucleus fails for Google AppEngine on Netbeans 8

This article explain how to prevent Datanucleus Enhancer failure when developing for Google AppEngine on Netbeans 8.

This article originally appeared on https://techtavern.wordpress.com

The cause

The Google AppEngine plugin for Netbeans from Gaelyk does not work with Google AppEngine Java SDK 40 and later.

The workaroud

While developing, use AppEngine Java SDK 37 (latest available version thar worked for me on Netbeans). You may download it from Maven Repository (see this link), as it is no longer available at Google Appengine site. When building the release version, compile against the latest AppEngine Java SDK.

Make sure that you compile against AppEngine Java SDK 37 and that the “Server” tab contains a server instance running on the same SDK, or the issues will get even worse.

The explanation

The latest (and very old) release AppEngine plugin for Netbeans 7.4, was last updated by Gaelyk at December 2013. After you add the local server to the “Server” tab, regardless if Datanucleus enhancement is turned on or not, the AppEngine plugin copies a set Datanucleus jars into your build directory, overwriting the correct ones, or adding jars you did not want if you did not need such dependencies. This jars do not work with the most recent Google AppEngine SDK or do result in classloader conflits.

There is no use trying to change build-impl.xml or ant-deploy.xml scripts within yout project. They contain an evident incorrect condition to copy the wrong or unwanted Datanucleus jars. But the AppEngine plugin does not run this scripts, it runs a similar hard-coded script that is copied int a temporary directory each time you run your local server.

The Symptoms

On the Netbeans “Output” tab:

Buildfile: C:\Users\Daniel\AppData\Local\Temp\build_appenginepluginutils_runanttarget_Server.xml

copyjars:

datanucleusenhance:

copyjars-v2:
     [copy] Copying 1 file to G:\cosmetopeia-release\Server\build\web\WEB-INF\lib

datanucleusenhance-v2:
  [enhance] Encountered a problem: Unexpected exception
  [enhance] Please see the logs [C:\Users\Daniel\AppData\Local\Temp\enhance5567238914045160848.log] for further information.

On the log file:

java.lang.RuntimeException: Unexpected exception
    at com.google.appengine.tools.enhancer.Enhancer.execute(Enhancer.java:76)
    at com.google.appengine.tools.enhancer.Enhance.<init>(Enhance.java:71)
    at com.google.appengine.tools.enhancer.Enhance.main(Enhance.java:51)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.google.appengine.tools.enhancer.Enhancer.execute(Enhancer.java:74)
    ... 2 more
Caused by: java.lang.NoSuchMethodError: org.datanucleus.plugin.PluginManager.<init>(Lorg/datanucleus/PersistenceConfiguration;Lorg/datanucleus/ClassLoaderResolver;)V
    at org.datanucleus.OMFContext.<init>(OMFContext.java:159)
    at org.datanucleus.enhancer.DataNucleusEnhancer.<init>(DataNucleusEnhancer.java:172)
    at org.datanucleus.enhancer.DataNucleusEnhancer.<init>(DataNucleusEnhancer.java:150)
    at org.datanucleus.enhancer.DataNucleusEnhancer.main(DataNucleusEnhancer.java:1157)
    ... 7 more

 

Unit tests for Objectify entities and DAOs

This article explains how to write unit tests (with junit) for Objectify entities and DAOs.

Google Cloud Platform describes how to write unit tests for Google DataStore. A small adaptation enables the same solution for Objectify.

The documentation suggest creating a LocalServiceTestHelper to be initialized in a @Before/setUp() method and disposed in a @After/tearDown() method.

After calling helper.setUp(), initialize the objectify framework by calling ObjectifyService.begin(). Store the returned closable so you can dispose Objectify at the end of your test.

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.googlecode.objectify.ObjectifyService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class EventDataStoreTest {

    private final LocalServiceTestHelper helper
= new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
    private com.googlecode.objectify.util.Closeable closeable;

    public EventDataStoreTest() {
    }

    @Before
    public void setUp() {
        helper.setUp();
        closeable = ObjectifyService.begin();
    }

    @After
    public void tearDown() {
        closeable.close();
        helper.tearDown();
    }

    @Test
    public void testSaveCriterioBuscado() {
        EventoDataStore.createInstance().save(
new EventEntity("a", "b", "c", "d"));
    }
}

Displaying PDF documents on Netbeans RCP

This article explains how to display PDF documents on Netbeans RCP, using JasperReports libraries.

This article appeared first at techtavern.wordpress.com.

The proposed solution uses a TopComponent instance in editor mode to display each PDF document. It assumes that there is a JasperPrint object that represents a page-oriented document that can be viewed, printed or exported to other formats. The TopComponent will use a JRViewer to render the PDF document. The JRViewer handles scrolling, zooming, paging and even printing and saving as PDF, Excel and other formats.

Required libraries

Your module (or some depended module) requires to import JasperReport and POI libraries. Following JARs worked for me and were downloaded from Maven Central Repository: jasperreports-6.0.0.jar, commons-codec-1.5.jar, dom4j-1.6.1.jar, stax-api-1.0.1.jar, poi-ooxml-schemas-3.10-FINAL-20140208.jar, poi-scratchpad-3.10-FINAL-20140208.jar, poi-3.10-FINAL-20140208.jar.

Create an empty TopComponent

Create a TopComponent named “JasperViewerTopComponent”. Add no content to it. Change annotation as shown above:

@TopComponent.Description(
    preferredID = &quot;JasperViewerTopComponent&quot;,
    iconBase = &quot;br/.../libs/jasper/JasperViewerTopComponent&quot;,
    persistenceType = TopComponent.PERSISTENCE_NEVER
)
@TopComponent.Registration(mode = &quot;editor&quot;, openAtStartup = false)

Some comments about the annotations:

  • Remove @ActionID, @ActionReference and @TopComponent.OpenActionRegistration annotations that are generated automatically by the IDE. This annotations would create a menu entry in the “Window” menu to open a singleton TopComponent, that means, there would be only one TopComponent to display one PDF file at time.
  • Also remove @ConvertAsProperties annotation and edit @TopComponent.Description to set persistenceType to TopComponent.PERSISTENCE_NEVER. The original persistence type works only for singleton TopComponents.
  • Also remove @Messages annotation, as TopComponent title and tooltip will be set manually.
  • @TopComponent.Registration(mode = “editor”, openAtStartup = false). A TopComponent of mode “editor” is arranged centrally, surrounded by other non-editor TopComponents. This is usually the best alternative.

Change constructor

Replace the empty argument constructor by following constructor:

public JasperViewerTopComponent(String title, JasperPrint jasperPrint) {
  this.jasperPrint = jasperPrint;
  initComponents();
  setName(title);
  setToolTipText(title);
// CAUTION:
// Import net.sf.jasperreports.view.JRViewer
// Won't work with net.sf.jasperreports.swing.JRViewer
  JRViewer viewer = new JRViewer(jasperPrint);
  JRSaveContributor[] contrbs = viewer.getSaveContributors();
// Remove save-as features you don't want.
// This example keeps only save as PDF and Excel options.
  for (JRSaveContributor saveContributor : contrbs) {
    if (!(saveContributor instanceof JRSingleSheetXlsSaveContributor
     || saveContributor instanceof JRPdfSaveContributor)) {
       viewer.removeSaveContributor(saveContributor);
    }
 }
 add(viewer, BorderLayout.CENTER);
}

Some comments about the constructor:

  • There are two classes named JRViewer. Make sure to use net.sf.jasperreports.view.JRViewer!
  • JRViewer will take care of scrolling, zooming, paging and even printing and saving as PDF, Excel and other formats
  • It is simpler to create the JRViewer within the constructor instead of within the initComponents() method. As initComponents() is maintained by Matisse, you would have to use an attribute to pass the jasperPrint instance to JRViewer constructor.

Create utility method to open PDF files

public static void openReport(final String title, final JasperPrint jasperprint) {
  SwingUtilities.invokeLater(new Runnable() {
    @Override
    public void run() {
      JasperViewerTopComponent tc = new JasperViewerTopComponent(title, jasperprint);
      tc.open();
      tc.requestActive();
    }
  });
}

That’s it.

HttpURLConnection and GET query parameters

I regret that JAVA SE still lacks any decent native HTTP client framework! Simple use cases like GET requests with query parameters require handcrafted coding with HttpURLConnection.

This article appeared first at techtavern.wordpress.com.

HttpURLConnection misses methods to set query parameters. There is no URI builder facility as found on Apache HTTP Client. You are on your own.

Therefore, I wrote the applyParameter method that takes care of GET query parameters.

URI baseUri = new URI("www.exemple.com/search");
URI uri = applyParameters(baseUri, "word","java");
HttpURLConnection connection = 
    (HttpURLConnection) uri.toURL().openConnection();
connection.setDoInput(true);
connection.setDoOutput(false);
connection.setRequestMethod("GET");
connection.connect();
if (connection.getResponseCode() == 
   HttpURLConnection.HTTP_OK) {
   // ...
}

The applyParameters method looks like:

URI applyParameters(URI baseUri, String[] urlParameters)
   StringBuilder query = new StringBuilder();
   boolean first = true;
   for (int i = 0; i < urlParameters.length; i += 2) {
     if (first) {
        first = false;
     } else {
        query.append("&");
     }
     try {
        query.append(urlParameters[i]).append("=")
             .append(URLEncoder.encode(urlParameters[i + 1], "UTF-8"));
     } catch (UnsupportedEncodingException ex) {
        /* As URLEncoder are always correct, this exception
         * should never be thrown. */
       throw new RuntimeException(ex);
     }
   }
   try {
      return new URI(uri.getScheme(), uri.getAuthority(), 
         uri.getPath(), query.toString(), null);
   } catch (URISyntaxException ex) {
     /* As baseUri and query are correct, this exception
      * should never be thrown. */
      throw new RuntimeException(ex);
   }
}

I had to bother about catching two exception that I know that will never be thrown: URISyntaxException and UnsupportedEncodingException. I had to join parameters by hand into a string. I had to care about safe web encoding. Shame on you JAVA!

Fortunately, at least I figured out that the URI constructor allows to append the query string to the base URI.