• Aug
    • 01
    • 2013

JavaFX on iOS using RoboVM and Maven

Posted by In JavaFX, RoboVM

A bit of background

Oracle have open sourced JavaFX and included in it code that allows JavaFX to run on iOS. The only problem is they haven’t provided a way to actually run Java on iOS, which, as you can imagine, is something of a necessary first step. It’s a little bit like giving us an awesome new type of high speed rail engine – we just have to build the train that the engine goes in, and lay the tracks, and build the stations, and provide the fuel, and …

For reasons many of us don’t understand and certainly don’t agree with, Oracle have decided that the huge, complex and difficult task of getting a Java VM running on iOS is something the community should do. While there have been many loud and, let’s say, “passionate” responses and objections to this, there’s not likely to be any change on this from Oracle anytime soon.

So the community are doing what they can. Niklas Therning, from Trillian AB, has been working hard on a Java VM that runs on iOS and has released a very early access version of RoboVM. This is not the only Java iOS option but it is the one that is most ready to use with JavaFX. RoboVM compiles Java bytecode into native code that can be run on iOS. It is based on the Android Java Runtime so it doesn’t support all Java features but it comes close enough for our purposes.

Unfortunately the version of JavaFX that has been open sourced runs only on Java8 (not yet released) and RoboVM currently only runs on Java7.  To solve this part of the problem Danno Ferrin has created a backport of the Java8 version of JavaFX to run on Java7. In turn, I’ve taken the awesome work done by Niklas and Danno and combined this into a Maven plugin for RoboVM, that has special support for JavaFX in it to ease the developer workflow.

The end result has just been released and below I give you the (hopefully simple) steps to get your own JavaFX app running on iOS!

A word of warning: this is at best an alpha release! It is slow, it is buggy and the process is still clunky in places. The good news however is that there is plenty of room for things to be optimised, cleaned up and improved. Things can only get better – so don’t judge the platform by what you see now, judge the potential.

Business Time

Before getting started:

You need to have JavaFX on your classpath, so use the JavaFX Maven plugin to do this by running the following command line:

  • mvn com.zenjava:javafx-maven-plugin:2.0:fix-classpath

On mac you will probably have to run this using sudo.

Then simply check out the sample JavaFX RoboVM app:

And from the command line run your choice of:

  • mvn robovm:iphone-sim  (runs the app in the iphone simulator ) 
  • mvn robovm:ipad-sim  (runs the app in the ipad simulator)
  • mvn robovm:ios-device  (runs the app on whatever iOS device is plugged in)

That’s it!

Note: the sample app will only run on the latest iOS so make sure your device is fully updated. This is an aspect of the sample app, not RoboVM, eventually we will provide docco on how to run on older versions of iOS.

Currently there’s no automated way to generate an app bundle for the app store. The focus is on improving the performance and functionality at this stage, and commands for building App Store bundles will be added.

A little more detail

The sample app is a very simple JavaFX “Hello World” app that is setup to build and run with RoboVM. It’s designed to be a kickstarter template – take this code, copy it into your own project, and then expand on it to build whatever you want.

RoboVMSampleJFXApp.java is the entry point for the JavaFX app. From here down you can insert your own JavaFX app and make whatever you want.  In theory you should be able to more or less ignore the fact that you are in a iOS app and just build a JavaFX app. In practice, especially in this alpha release, there will be areas where things fall down but you should be able to do most of the standard JavaFX stuff.

RoboVMJFXLauncher.java is a special wrapper for your App and is the main entry point for RoboVM. It simply sets up your JavaFX application so it can run on iOS. Take a straight copy and paste of this into your own project and just change the name to be whatever you want, etc. Future versions of the Maven plugin will likely generate this class for you so you won’t have to include it, but for now just copy it in.

The project’s POM has a couple of RoboVM specifics but it is not too bad. The RoboVM Maven plugin is included and configured, and there are some dependencies onto the RoboVM runtime libraries, which you need at this stage. These dependencies may not be necessary in future versions of the plugin if we can find a way to cleanly hide them.

There are many configuration options for the RoboVM Maven plugin, letting you do things like modify your plists or set the signing agent, etc. Currently these are not well documented and you’ll have to work off the code. Documentation is on the todo list.

Where to from here

No doubt there will be many teething problems and issues. Be gentle with it, feedback is probably best first discussed on the openjfx mailing lists: http://mail.openjdk.java.net/mailman/listinfo/openjfx-dev as it will be hard at first to know what is a JavaFX issues and what is a RoboVM issue. It will be good for the Oracle JavaFX guys to see what problems are coming up anyway.

As I mentioned at the start, this is a rough, early-access, alpha release. We know it is not performant, we know it has rendering and layout issues, we know it’s a long way from perfect. Things will improve massively from here. How fast it improves depends on how much time those working on it can contribute – this is currently a completely unfunded community effort by volunteers in their free time.

If you want a robust, production-ready version of all this then you have two choices:

  • 1. Wait for it to happen
  • 2. Contribute – either time/code or talk to Niklas about how you can help sponsor his work financially

Continue Reading→
    • Jul
    • 01
    • 2013

JavaFX Maven Plugin 2.0 Released

Posted by In JavaFX

I’ve just released version 2.0 of the JavaFX Maven Plugin. The details of what’s changed in this release (a lot) are mostly covered in my previous post on the topic.

The really great news with this version of the plugin is that I’ve now got everything fully documented and the documentation is released as part of the normal release process now. You can (hopefully) find everything you need to know about the plugin at: http://zenjava.com/javafx/maven.

A big thanks to everyone who tested the plugin and gave feedback! Much appreciated!

I hope you guys find this plugin useful. As usual, post issues to the GitHub issues area: https://github.com/zonski/javafx-maven-plugin/issues

Continue Reading→
    • Jun
    • 09
    • 2013

RoboVM Maven Plugin (beta release)

Posted by In JavaFX, RoboVM

RoboVM is currently one of the biggest contenders for getting JavaFX running on iOS. As such, I’ve been working with Niklas (the guy behind RoboVM) to develop a Maven plugin for it so it’s easier to get started and build RoboVM platforms.

The plugin is now in beta release and pretty much ready to go live. It does not yet include JavaFX support as we are waiting on the final parts of JavaFX to be open sourced. Once this is done however, the intent is to extend this plugin to allow easy building of JavaFX apps for iOS running with RoboVM.

If anyone wants to have a play with the current (non JavaFX version of the plugin) and test and feedback that would be much appreciated. You need to be on a Mac and have XCode and LLVM installed (as per these steps), but you do not need to install any RoboVM components, the plugin will take care of this.

There is a very simple sample project you can copy here: https://github.com/robovm/robovm-sample-ios-app. Which is basically a Mavenized version of the RoboVM starter tutorial.

The interesting bit is, of course, the POM: https://github.com/robovm/robovm-sample-ios-app/blob/master/pom.xml

Once you have your project setup locally, simply run mvn robovm:robovm and your files will be found in target/robovm

I’m aiming to push this live before the end of the week, so any testing before then would be great.

Continue Reading→
    • May
    • 26
    • 2013

JavaFX Maven Plugin 2.0 alpha – Feedback needed

Posted by In JavaFX

It’s fair to say my existing JavaFX Maven plugin code is downright ugly. A combination of trying to cover too much ground, knowing too little about Maven at the time, and having to work around all the horrible nasties in the current packaging tools led to a code base that was pretty much unusable. I’m pretty impressed that a few of you managed to contribute fixes and enhancements!

I’ve been holding off fixing things however, as there were rumours that the JavaFX packaging guys were planning to significantly rework their tools, finally giving it the attention it desperately needs. Sadly, it seems that the packaging guys have again been dragged off into the land of tedious bug fixing. So I’ve given up waiting, and I’m now a good way through overhauling the JavaFX Maven plugin.

I’ve just put up a a 2.0 alpha-release of the plugin. This is a complete re-write from the ground up and it takes some radically different approaches. Given these changes I’m looking for two things: I need people to test this new code (especially on multiple OS’s), and I need feedback on some of the more significant changes as to whether people think these are good or not. It’s not too late to change things if needed.

Below are the steps to needed to use the 2.0 snapshot release. Further down, I list some of the more significant changes to the code – if anyone is a previous contributor, or wants to be a future contributor, you may want to read through this.

One of the good things about the new code base is that I can now generate the standard Maven docco for it (although I still need to actually document a lot of things). I’ve uploaded the current version to here: http://zenjava.com/javafx-maven-plugin/plugin-info.html

Using the 2.0 Snapshot Release

Add the Sonatype Snapshot repository to your POM:

    <pluginRepositories>
        <pluginRepository>
            <id>sonatype.snapshots</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
            <releases>
                <updatePolicy>daily</updatePolicy>
            </releases>
            <snapshots>
                <updatePolicy>daily</updatePolicy>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

Change your POM to use version 2.0-SNAPSHOT of the JavaFX Maven Plugin. In general most of the configuration options are unchanged, but if you’re using signed web-based deployments, you also need to change the ‘permissions’ element to ‘allPermission’. This is more inline with the way the JavaFX Packaging tools need it to be.

    <plugin>
        <groupId>com.zenjava</groupId>
        <artifactId>javafx-maven-plugin</artifactId>
        <version>2.0-SNAPSHOT</version>
        <configuration>
            <mainClass>com.yourcompany.YourApp</mainClass>
            <allPermissions>true</allPermissions>
            ...
        </configuration>
    </plugin>

Additionally the commands for building things have changed. Partly this is to be more in-line with Maven’s naming conventions but also some commands have merged or been significantly changed. Here’s the list of commands:

mvn jfx:jar — this replaces build-jar, unlike before it does not merge everything into one uber-jar, instead it creates a ‘lib’ directory for dependencies and the manifest references these
mvn jfx:native — this replaces build-native and will build a native installer based on the settings
mvn jfx:web — this replaces build-webstart and it now uses the JFX packaging tools directly instead of templates, it build both a webstart and applet output
mvn jfx:fix-classpath — puts jfxrt.jar on your JVM’s classpath, same as before
mvn jfx:generate-key-store — generates a testing key store for you, same as before
mvn jfx:run — starts your app, same as before

That’s it at this stage. From a user’s perspective, the main thing is we don’t bundle everything into an uber-jar anymore (this was the root cause of many problems for many people).

Additionally I am now using the JavaFX Packaging tools directly for the web stuff, which is both good in that you get more features (like applets, etc) but bad in that you lose the powerful templating system that I had before. I’ve done this mostly for ease, and if people really want the templating system back in, let me know and we can look into it.

What I really need is for you guys to have a crack at using this new plugin and the new commands and tell me firstly if it works or blows up, and secondly if you like it or hate it. You can either comment on this post, or raise issues in GitHub.

Changes in the 2.0 Codebase

If you were one of those brave few who managed to work out the old codebase, the good news is, the new stuff is MASSIVELY simpler. The new codebase is now down to a few, nice, self-contained classes: https://github.com/zonski/javafx-maven-plugin/tree/master/src/main/java/com/zenjava/javafx/maven/plugin

Firstly, I’ve done the reflection completely differently. So now the code all makes direct calls onto the JavaFX packager library, instead of using the script-style invoke(target, “method”, param1, param2). This alone should make it significantly easier to work with the code, and people should be able to make contributions much more easily.

The next big change is that I’ve ditched the idea of using the javafx-deploy-lib. The point of this was to allow re-use between multiple build tools so others (such as those writing the Gradle plugins, etc) could try and use it. Turns out no one was too interested in doing this, and since it just complicated things for us in Maven land, I’m dropping it. Any code that was needed from this has now been brought into the plugin code base and references to the deploy lib are gone.

Additionally, I’ve also improved the inheritance hierarchy between the different Mojos. So each one now defines it’s own parameters instead of them all being defined in a common base class. This means each Mojo now only lists the parameters that are relevant to it, and the generated documentation now starts to be useful (before it looked like every param could be used everywhere).

Finally, I’ve done away with the uber-jar code. This previously unpacked and re-jarred all the dependencies into one big, executable jar. It seemed like a good idea at the time but turns out there are libraries out there that have files named the same way in them, and things like manifests get quickly trashed. All in all, this approach was a pain. The new approach now just uses an external ‘lib’ directory where all the runtime jars are copied to, and the manifest in the main app jar points to this.

There’s a few other minor changes and fix-ups that I’ve done on the way, but these are the main things. It would be great to get some feedback on the updated code. I’m also hoping that this will open up the door to more people contributing, as it is now quite trivial to wire in a parameter or setting that’s not done yet. If you need it, add it.

Continue Reading→
    • Feb
    • 21
    • 2013

JavaFX Maven Plugin 1.5

Posted by In JavaFX

I’ve released version 1.5 of the JavaFX Maven Plugin:

The two big fixes in this one are:

  • The ‘app name’ is now being set correctly. Previously this was causing the build to fail when generating native bundles for Mac.
  • ‘src/main/deploy’ is now included in the classpath for the build tool, meaning you can add anything in there that the JFX packaging tools need. Among other things, this allows you to provide your own custom icons for native bundles (see the JFX deployment tools for details on how to do this).

Thanks to everyone who made contributions for version 1.4 and 1.5 – most of these fixes have come from the community or as  a result of community feedback and input.

As I mentioned in my last post, I’m off travelling for a couple of months and won’t be doing anything on this plugin during that time. I am intending to integrate it with the work the JavaFX deployment guys are doing when I get back though. So keep raising issues and requests and we’ll see what we can do with them in a few months time.

I’ll be blogging my travels a little bit too, so if you’re after a distraction check out:

http://lifefirsthand.com

Enjoy

Continue Reading→
    • Feb
    • 15
    • 2013

JavaFX Maven Plugin 1.4

Posted by In JavaFX

I’ve just merged in contributions from the community on the JavaFX Maven plugin and released these as version 1.4.

Nothing else was changed apart from these contributions. I have been working with the JavaFX deployment guys trying to integrate and improve things using their recently open sourced packaging tools but the build setup for this is a mess, slowing everything down to a crawl. This work has also sucked up the small amount of free time I had available to put into this project recently.

I’m soon to be off travelling and will be offline for a good few months. If you wanted something fixed that didn’t make it into this build, there’s about a week left to make that happen. In particular, I think there is still an issue on Mac native builds. If someone wants to contribute a fix for this I will merge it, otherwise there’s a 50-50 chance I’ll put a fix in myself before I go.

If you have anything else you want fixed, raise it as an issue on the GitHub project. If you provide a fix you have a much higher chance of it getting sorted.

Continue Reading→
    • Dec
    • 04
    • 2012

JavaFX with a SpringMVC REST server in 5 minutes

Posted by In Enterprise, JavaFX

Creating a simple JavaFX application in 5 minutes was pretty fun, but we can do better. This post shows you how to use the javafx-rest-archetype to create a skeleton project that includes both a REST server (based on SpringMVC) and a JavaFX client that talks to that REST server.

As before you need to have already installed JDK 7 (update 6) or later, and Maven 3.0 or later (make sure you set the JAVA_HOME environment variable when doing this).

Step 1: Fix your classpath

If you did this last time then you do not need to do it again!

When you install the JDK currently, it includes JavaFX but does not put it on the classpath (yes this is weird – it will be fixed in Java 8). To fix this, open a Command Prompt and type:

mvn com.zenjava:javafx-maven-plugin:1.3:fix-classpath

This will ask you to confirm that you want to do this, type “y”.

Step 2: Create a New Project

Open a command prompt and go to a new workspace area (i.e. create a new directory to house your code), then type:

mvn archetype:generate -DarchetypeGroupId=com.zenjava 
    -DarchetypeArtifactId=javafx-rest-archetype -DarchetypeVersion=1.1

You will be prompted for some parameters:

  • groupId: com.mycompany
  • artifactId: my-rest-app
  • version: leave as default
  • package: com.mycompany.myrest

This will create a new directory called “my-rest-project” with some basic source code which you can use as a skeleton to get you up and running quickly.

Step 3: Build the project

Change directory into your newly created project and use Maven to build your application – this will build both the client and server:

cd my-rest-app
mvn clean install

Step 4: Start the server

Change directory into the server sub-directory and start a Jetty server to run your REST web server:

cd my-rest-app-server
mvn jetty:run

You do not have to have Jetty installed, Maven will take care of this for you. Jetty is very useful for getting up and running quickly like this, however you don’t have to use Jetty. Maven builds a standard WAR file that you can deploy into any JEE web server, such as Tomact, JBoss, etc.

Your server should now be running. You can test it using a standard web browser to http://localhost:8080/my-rest-app-server/welcome and you should see XML output like:

Step 5: Run the client

Open a new command prompt, change directory into the client sub-directory and start your JavaFX client:

cd my-rest-app\my-rest-app-client
mvn jfx:run

When the JavaFX client starts up enter your name and hit the “Say Hello” button. The response from the server will be shown in the text area below:

Step 6: Edit your project

You now have a working skeleton project which you can build upon to create your next awesome JavaFX and REST server application. Since you are using Maven, you can simply open the base level POM file in your favourite IDE and your project will be automatically configured for you with the classpath all setup, etc.

Here’s the links again for using Maven with your IDE:

Your project tree should look something like (this is taken from IntelliJ):

Hopefully the code is pretty self explanatory, and I have included JavaDoc with links to external help documentation in most cases. If you need more help, post comments here or raise issues in the GitHub project for the archetype: https://github.com/zonski/javafx-rest-archetype/issues

Step 7: Distribute your app

Distributing your server is very easy, just take the generated WAR file from the ‘target’ directory of your server module and copy this to any JEE web server. I use and highly recommend Tomcat but you can use whatever you like. In tomcat simply download and install Tomcat (i.e. unzip it) onto your server and then copy the WAR file into the webapps directory. Tomcat will take care of the rest – server side deployment is easy these days.

As with the simple JavaFX application example, you can choose any of the JavaFX deployment options such as Webstart, native installer, executable JAR file, etc. They each have their advantages and disadvantages but refer to my previous post for details on this.

All the same commands are available as in the simple JavaFX project, just make sure you run them from within the client module. For example to build an executable JAR use:

cd my-rest-app\my-rest-app-client
mvn jfx:build-jar

And an executable JAR will be built into the target directory of the client module for you to copy and distribute however you want.

Conclusion

Using this archetype it is very easy to get up and running quickly with a simple JavaFX client and SpringMVC REST server. All of the code generated can be modified, added to and deleted to suit your own project needs so there are no dependencies or links to any special classes of my creation in the project. This simply provides a starting shell for you to do with as you please.

This example is deliberately simple. It does not include security or database setup, both of which would be pretty much standard in your typical REST client-server application. If I get time I and/or there is strong interest in seeing a REST setup with security and persistence, I may add an additional archetype. Register your interest in this by commenting below.

REST is very popular because it provides a way for JavaScript/AJAX clients to make remote calls on servers. I’ve provided this archetype because AJAX is such a popular space, however there are better, faster and simpler options than REST when working with pure Java on both the client and server, so why compromise? I am planning to create another archetype that creates the same setup as this one but using Spring Remoting based on Spring’s HTTPInvoker. Hopefully I will have something going on this over the next month or two – again register your interest by commenting if this is something you would find useful.

If you have any problems or suggestions please comment or raise an issue on the archetype GitHub project:  https://github.com/zonski/javafx-rest-archetype/issues

Continue Reading→
    • Nov
    • 24
    • 2012

From Zero to JavaFX in 5 minutes

Posted by In Enterprise, JavaFX

Over the last few weeks, I’ve been working on a new Maven Plugin for JavaFX. Using this plugin it’s much, much easier to get up and running quickly and to build complicated distribution bundles (such as executable JAR files, native installers and webstart bundles). Rather than tell you about it, here’s the steps to take you from nothing to a fully working, packagable JavaFX distribution.

Before starting you will need to have installed JDK 7 update 6 or later, and Maven 3.0 or later. This guide will walk you through that.

Step 1: Fix your Classpath

When you install the JDK currently, it includes JavaFX but does not put it on the classpath (yes this is weird – it will be fixed in Java 8). To fix this, open a Command Prompt and type:

mvn com.zenjava:javafx-maven-plugin:1.3:fix-classpath

This will ask you to confirm that you want to do this, type “y”.

You will need to do this only once for each development machine (and you will never need to do it on your users’ machines, only development ones).

Step 2: Create a New Project

Open a command prompt and go to a new workspace area (i.e. create a new directory to house your code), then type:

mvn archetype:generate -DarchetypeGroupId=com.zenjava /
-DarchetypeArtifactId=javafx-basic-archetype /
-DarchetypeVersion=1.1
You will be prompted for some parameters:
  • groupId: com.mycompany
  • artifactId: my-jfx-project
  • version: leave as default
  • package: com.mycompany.myjfx
This will create a new directory called “my-jfx-project” with some basic source code which you can use as a skeleton to get you up and running quickly.

Step 3: Run the Project

Change directory into your newly created project and use Maven to launch your application:

cd my-jfx-project
mvn jfx:run

You should see a basic hello world style dialog popup like so:

Note: this run command is just a pure wrapper around the maven exec plugin. There is no special JavaFX magic going on here, it’s just a convenience method.

Step 4: Edit the Project

You can now open the new project in your favourite IDE:

Since this a Maven application any dependencies you need will be downloaded, your classpath will be all setup for you and you simply can get stuck in and start editing, running and debugging your project.

The starter project includes some example FXML, basic logging and makes use of the very cool MigPane layout manager. All of this is just a guide, you can delete, edit, rename or do what you like to all of this code to create your perfectly customised JavaFX app.

Your project should look something like this, with MainApp being the main entry point that you run:

For tips on writing JavaFX applications see: http://docs.oracle.com/javafx/

For tips on using Maven to manage your dependencies: http://maven.apache.org/guides/getting-started/index.html

Step 5: Distribute your App

JavaFX applications can be deployed in a number of different ways – each option has it’s benefits and drawbacks - choose whichever option suits you.

Executable JAR file 

From the command line, and in the base directory of your project (where the POM file is) type:

mvn jfx:build-jar

Your executable JAR will be found at: {project.dir}/target/my-jfx-project-1.0-SNAPSHOT-jfx.jar

The Executable JAR file option is simple and handy as you can easily send the single JAR around to users and they can just double click it. It does require Java to be already installed on the user’s system though, and there’s no support for auto updating and advanced functions (like menu shortcuts, file associations, etc). Also be aware that if your jars contain files with the same name then they will clobber each other (see this issue)


‘BAT’ file (for Windows)

From the command line, and in the base directory of your project (where the POM file is) type:

mvn jfx:build-win-bat-bundle

A directory containing a Windows batch file  and all project JARs will be found at: {project.dir}/target/win-bat

The ‘BAT’ file option is great for quick demos, you can easily copy the directory to your demo machine and tweak the JAR files and scripts as needed. It does require Java to be already installed on the user’s system and java.exe being on the path, so it’s more for internal use than general end user consumption.


Webstart 

Webstart applications can either be signed, or unsigned. If they are not signed then they are limited by the browser’s sandbox. Most interesting applications will need to be signed and to do this you need to get a certificate to verify you are who you say you are.  For development this can be a pain, so the JavaFX Maven plugin includes the ability to generate a quick certificate for testing (i.e. you will be able to deploy but your users will see a “this app is not trusted” message when they run it).

To generate a certificate run the following command in the base directory of your project (change the domain, country code and state to match your details):

mvn jfx:generate-key-store -DcertDomain=com.yourcompany -DcertCountry=US -DcertState=WASHINGTON

You only need to do this once for your project and you can optionally check this development keystore into your source control so other developers can avoid this step.

To then build a Webstart bundle run:

mvn jfx:build-webstart

A directory containing  a HTML file, JNLP file and your signed JAR files will be found at: {project.dir}/target/webstart

You can copy this directory directly to any web server and point users at the HTML file to run your app. Both the HTML and JNLP files are built from velocity templates which you can customise to be anything you want.

The web-based distribution, and auto updating of Webstart are great in theory, but it doesn’t work that well in practice. Use webstart only if you have control over the operating environment (e.g. you are running within a corporate IT network and can control Java versions, etc). Don’t try to use it for delivery to the general public – it just won’t work and will cause you lots of grief.
 Some other things to be aware of with webstart  depoyment:
  • browser security issues means Java will always be auto-updated, your app may break at any time if a future version of JavaFX changes the way your app works
  • there is a trend away from browser plugins and this option is not likely to be supported on major browsers soon


Native Installers

To build the native installer for your current OS run:

mvn jfx:build-native

Your bundle will be built into: {project.dir}/target/bundles

Currently native installers require you to first manually download and install an extra packaging tool specific to your OS. e.g. on Windows you need to install WiX. See the JavaFX packaging guide for more info.

Native installers are a recent addition to JavaFX, basically wrapping your Java program in a native launcher for your platform (i.e. an ‘exe’ on Windows) and bundling this into a native platform installer (i.e. an ‘MSI’ package on Windows). The JRE is actually co-bundled into the installer so your user just installs your application and does not need to install Java separately. From the end user’s perspective the whole thing is ‘native’ and the fact that your app is a Java app is hidden from them completely.

This is a very powerful option and has a lot of promise (and is the approach I would recommend) but there are still a couple of drawbacks. The main one is that you can only build the distributable for the target platform on that platform, i.e. you can only build a Windows MSI on a Windows machine, and a Mac PKG on a Mac machine. Additionally you have to download and install a third-party packaging tool that the JavaFX packing tool uses under the hood (i.e. on Windows you have to download and install WiX). I am working on extending the Maven plugin to do this last step for you.

The other major drawback is that the JRE is currently very large (70MB+) making your distributables large. This is something the Java community is (very slowly) working on, and once I get the other parts of this plugin working I may also put some effort into fixing this – it should be possible to get the bundle size under 20MB.

Finally there is no auto-updating code as yet, so each release your users must uninstall the old version and install the new one. Again this is something on the JavaFX to-do list but it hasn’t got a lot of attention. If it is something that you want, best to raise it in the open JFX mailing list.

Native installers is where I intend to spend the most effort going forward as I believe it is the only real long term option for JavaFX. I plan to release a version of the Maven plugin that can automatically download WiX for you sometime in the next few weeks. After that I will look at options for doing something similar for Mac and Linux bundles. 

Conclusion

The Maven Plugin makes it simple to get up and running with JavaFX and to build distribution bundles.  Your projects are also IDE independent (i.e. developers can use IntelliJ, Eclipse or NetBeans on the same project without problems) and you get all the cool dependency assistance that has made Maven such a popular tool. There’s little reason not to use it.

The Plugin is quite new however and since I develop on Windows, it has not been tested on Mac and Linux. If you run into any trouble on these other platforms, please raise an issue (general testers on these platforms would be great).

Currently the plugin does not support Applets. This would be trivial to add but since Applets are virtually a dead technology I have not bothered. If this is something you would like to use, raise an issue.

I also have not added Mac/Linux shell script options (i.e. similar to the Windows BAT option). Again these would be trivial to add but I don’t have environments to test on. If you have a need for these, raise an issue (and by doing so you volunteer to be the tester).

Several more advanced features are either missing (e.g. including native third-party libraries) or not well documented (e.g. templating HTML and JNLP files). I will tick away at these as and when I have time and motivation. I’m just one guy working on this in my free time, after-hours though – there’s plenty of work to be done here so if anyone is interested in volunteering some time, let me know.

Continue Reading→
    • Mar
    • 27
    • 2012

JavaFX and Spring Security

Posted by In Uncategorized

This entry is part 15 of 15 in the series Building JEE applications in JavaFX 2.0

Disclaimer: Security is a big topic! This post is going to introduce a very basic Spring Security setup. You could probably just get away with this for a small system running on an intranet, or VPN, but for anything with serious security concerns you will need to explore the Spring Security docco and build on this post to suit your needs. At the very least you should look at encrypting passwords sent over the wire and use SSL as your socket layer. I will likely add some more complex setup to First Contact in future posts but until then check out other security articles on the web for more information, or post questions below. 

The great thing about the Internet is that it’s open to everyone. The bad thing about the Internet is that it’s open to everyone. If we’re going to put our data out there on the web chances are we want to secure that data so only those who should see and edit it, can do so.

Security is a messy business. To make a system safe, you have to put up a lot of walls and add locks to every door. Just like securing your house, the more secure you want it, the more keys you have to carry around on your keyring and the more hassles you have getting in the front door yourself.

As usual however, Spring has a framework that will help us out in this area, namely Spring Security. And, as usual, this framework is very good – non-invasive, simple to use and easy to setup. The only challenge is that Spring Security really has come out of the webapp space, so there are a few not-so-common tricks we have to employ to make them work for our JavaFX, client-server based application.

As normal, we’ll start with our code base from the previous post and add to it. The full source code for this post can be found at: http://code.google.com/p/jfxee/source/browse/trunk/first-contact/first-contact-security/

Step 1: Add Spring Security dependencies 

Spring Security is made up of a number of modules of which we need only a couple. In our server POM we need to add the following dependencies:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>

Security is mostly transparent to the client, however when a security violation is triggered (such as when the user tries to access something they are not authorised to) then the server will throw an exception. In order for the client to be able to process these exceptions it must have them on the classpath. As such, we add the following to the common POM so that these core classes are shared between client and server:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>

Step 2: Secure the ContactService

There are a number of ways we can define which parts of our application are secured. In web applications it is fairly common to restrict access to certain resources via URLs, so that, for example, all pages under the ‘secure’ directory or all URLs ending with “-admin.htm” are restricted to certain roles. Spring Security supports a powerful expression language for defining this in XML.

We could do this for our client-server application since all of our Service methods are exposed as URLs by the HttpServiceExporter. Given that we are dealing mostly with the Service interfaces in Java and the URL is more or less transparent, a more natural fit is to use Spring Security’s annotation based configuration. There are a couple of different annotation options we can use, but the most powerful is the expression based ones added in version 3.0. With these we can define @PreAuthorize annotations to force authentication before our method is actually executed:

@PreAuthorize("hasRole('myrole')")
public String someSecureMethod()
{
    // this method can only be accessed by users who have 'myrole'
}

As well as being defined at the method level, @PreAuthorize can be applied at the class level, in which case it will apply to all methods within that class.  If a user tries to access any secured method without having the correct role, then Spring Security will prevent this by throwing an AccessDeniedException back to the client.

The roles used in the system are completely up to us and defined through configuration (we will configure our roles in the next step). For our application, let’s define a role called ‘USER’ and another called ‘ADMIN’. In order to use our system at all, a user must have the ‘USER’ role, and in order to add or edit data the user must have the ‘ADMIN’ role.

We can secure our ContactServiceImpl by simply adding the following annotations:

import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasRole('USER')")
public class ContactServiceImpl implements ContactService
{
    public List searchContacts(String[] keywords)
    {
        ...
    }

    public Contact getContact(Long contactId)
    {
        ...
    }

    @PreAuthorize("hasRole('ADMIN')")
    public Contact updateContact(Contact updatedContact)
    {
        ...
    }
}

(see the full source code for this class here)

Step 3: Configure Spring Security

Securing our Service is all good and well, but we still need to configure Spring Security to tell it how users should be authenticated and the type of security setup we want. Since our server is a web-based application (which our client connects to over HTTP) we can make use of a lot of the standard Spring Security web stuff.

The web implementation of Spring Security uses a HTTP Filter to intercept calls to the server and manage the security access, so we need to setup this Filter in our web.xml by adding the following config:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Next we need to add some security configuration to our Spring XML configuration (i.e. first-contact-servlet.xml). There’s a little problem here however, in web-land our Filter and our Servlet are defined as two separate entities, which means our Servlet-specific XML file won’t apply to the Filter.

We need to create a new XML configuration file; one that will apply to our application as a whole, not just our servlet. Luckily Spring defines a way to do this via the ContextLoaderListener, which is configured by a ‘context-param’. To do this we add the following to our web.xml file:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/first-contact-app.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

(you can see the full web.xml file here)

This setup will now result in two files being loaded on startup. The first is ‘first-contact-app.xml‘, which should contain all our application-wide configuration, and the second is ‘first-contact-servlet.xml‘, which should contain the minimal configuration just for our servlet.

When there is only a single servlet in use (which is how we currently have things) then the separation is not such a big deal, but when you have multiple servlets in place (for example if you add a SpringMVC or Struts web front-end to your application), then it’s important to ensure the core configuration gets shared, while the servlet specific stuff is not. In general, I put all the service, database, transaction and security configuration in the ‘app’ configuration, and include only the HTTP service exporter configuration in the ‘servlet’ file.

Here are the two updated configuration files. Included is the information needed for the Security setup. I’ll talk through this in detail below.

first-contact-app.xml

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

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:data="http://www.springframework.org/schema/data/jpa"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="

http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-2.5.xsd


http://www.springframework.org/schema/data/jpa


http://www.springframework.org/schema/data/jpa/spring-jpa.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-3.0.xsd


http://www.springframework.org/schema/security


http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.zenjava.firstcontact.service"/>

    <!-- Database Setup -->

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="first-contact" />
    </bean>

    <data:repositories base-package="com.zenjava.firstcontact.service"/>

    <!-- Transaction Setup -->

    <tx:annotation-driven/>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- Security Configuration -->

    <security:http create-session="always">
        <security:http-basic/>
    </security:http>

    <security:global-method-security pre-post-annotations="enabled"/>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider>
            <security:user-service>
                <security:user name="admin" password="admin" authorities="ADMIN,USER" />
                <security:user name="user" password="user" authorities="USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>

</beans>

first-contact-servlet.xml

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

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="/contact.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="contactServiceImpl"/>
        <property name="serviceInterface" value="com.zenjava.firstcontact.service.ContactService"/>
    </bean>

</beans>

So let’s look at the additional Spring configuration in more detail. First, we added the Spring namespace to the top of the XML file, no surprises here. Next we defined the ‘http’ element security like so:

<security:http create-session="always">
    <security:http-basic/>
</security:http>

This is a shortcut configuration for setting up all the normal Spring Security beans needed (and there are a number of them) for HTTP based authentication. If you want to understand all this in detail, take the time to read the Spring Security documentation, but in general you can just read this as ‘use web based security’ and move on with your life.

The only two special settings we’ve used are: the ‘create-session’ flag, which is needed to ensure a web session is created to store our authentication in; and the ‘http-basic’ setting, which tells Spring we’ll be using HTTP-BASIC AUTH for login – as you’ll see in the next section, we don’t actually use this but Spring needs to be told something here and this setting is the simplest to use.

Next, we tell Spring that we will be using annotation based authentication. As I mentioned earlier, there are a number of options, including Spring Security’s own @Secured annotation, and the JSR-235 annotations. We’re going to use the Pre/Post Authorization annotations however, and so we configure this like so:

<security:global-method-security pre-post-annotations="enabled"/>

Finally, we need to provide an AuthenticationManager. This is the core component that does user authentication (i.e. it logs users in and out and assigns them roles). In this simple setup we are going to use the most basic of AuthenticationManagers which allows us to hard code all our users and roles directly into the XML file, like so:

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider>
        <security:user-service>
            <security:user name="admin" password="admin" authorities="ADMIN,USER" />
            <security:user name="user" password="user" authorities="USER" />
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>

We’ve define two users, one called ‘admin’ and another called ‘user’ (with matching passwords). We’ve assigned each of these users the corresponding roles via the ‘authorities’ attribute. We can edit this file and add as many users and roles as we want – no other configuration is needed.

If your application has extremely simple security requirements then just maybe you will be able to get away with this setup. In most cases however, you will want to authenticate against a database of users, using encrypted passwords and secure communication channels. In even more advanced scenarios you might want to hook into an LDAP server or single-sign-on CAS server. Spring Security supports all of this (and so much more), and mostly this is achieved just by changing the above AuthenticationManager configuration to something more advanced. I will likely add database driven authentication with encrypted databases in a future post, but if you can’t wait for that check out tutorials like this one.

Believe it or not, we’ve just secured our application. If you try to run your server now and then connect to it with your client, you should get an AccessDeniedException stopping you from accessing all that sensitive contact information.

That’s great! Just one fairly major problem: we can’t yet login so that we can actually access that data.

Step 4: Add a Security Service

In our configuration above we told Spring Security that we wanted to use HTTP Basic Authentication. This was a bit of white lie to make Spring Security happy so that we could then go off and do our own thing. While we could use HTTP Basic Authentication, a simpler and more natural way for us to authenticate with our server is for us to make a simple Java call to a ‘login’ method on our Service, passing in our username and password. It turns out this is not too hard to implement in Spring Security.

First we create a new Service for this, we’ll call it the SecurityService, but the name is arbitrary and completely up to us. Like our ContactService, the SecurityService has a few aspects to it. Firstly the Service interface goes in our common module:

package com.zenjava.firstcontact.service;

public interface SecurityService
{
    void login(String userName, String password);
}

Then we add the implementation to the server module (we’ll leave the implementation body empty for now and deal with this below):

package com.zenjava.firstcontact.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.inject.Inject;

@Service
public class SecurityServiceImpl implements SecurityService
{
    private static final Logger log = LoggerFactory.getLogger(SecurityServiceImpl.class);

    public void login(String userName, String password)
    {
        // todo: login with Spring Security
    }
}

Next we can expose this service over HTTP via the first-contact-servlet.xml configuration file:

<bean name="/security.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="securityServiceImpl"/>
    <property name="serviceInterface" value="com.zenjava.firstcontact.service.SecurityService"/>
</bean>

And then in our client module, we can setup a connection to this service in our FirstContactAppFactory class. We need to refactor our code here slightly so as to be able to reuse the HTTP Request Executor:

@Bean
public SecurityService securityService()
{
    return createService("security.service", SecurityService.class);
}

@Bean
public ContactService contactService()
{
    return createService("contact.service", ContactService.class);
}

@Bean
public HttpInvokerRequestExecutor httpInvokerRequestExecutor()
{
    return new CommonsHttpInvokerRequestExecutor();
}

protected  T createService(String endPoint, Class serviceInterface)
{
    HttpInvokerProxyFactoryBean factory = new HttpInvokerProxyFactoryBean();
    String serverUrl = String.format("http://localhost:8080/%s", endPoint);
    factory.setServiceUrl(serverUrl);
    factory.setServiceInterface(serviceInterface);
    factory.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor());
    factory.afterPropertiesSet();
    return (T) factory.getObject();
}

With our SecurityService now available, we can look at implementing that login method. This involves two steps: first we use the AuthenticationManager (configured above) to validate the username and password and provide an ‘Authentication’ object for us. Second we need to place that ‘Authentication’ object (which represents the logged in user) in the SecurityContextHolder provided by Spring Security. Since we’re using the standard web-based setup, this SecurityContextHolder is associated with the current web session, so each client will be associated with a different instance.

The code for this turns out to be remarkably simple:

@Service
public class SecurityServiceImpl implements SecurityService
{
    private static final Logger log = LoggerFactory.getLogger(SecurityServiceImpl.class);

    @Inject private AuthenticationManager authenticationManager;

    public void login(String userName, String password)
    {
        // never log password information!
        log.info("Logging in user '{}'", userName);
        UsernamePasswordAuthenticationToken authToken
                = new UsernamePasswordAuthenticationToken(userName, password);
        Authentication authentication = authenticationManager.authenticate(authToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        log.debug("User '{}' successfully logged in with authorities: '{}'", userName, authentication.getAuthorities());
    }
}

Now all we need is a Login screen to call this from the client.

Step 5: Add a Login screen

Adding a Login screen is now fairly simple. The general flow is not overly different to our existing Contact management screens – we gather from data from the user and call a Service method.

First some FXML, I’m lazy, so I have adapted this from official getting started with FXML guide:

Login.fxml

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

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

<BorderPane fx:id="root" fx:controller="com.zenjava.firstcontact.gui.login.LoginPresenter" xmlns:fx="http://javafx.com/fxml">

    <center>
        <GridPane alignment="top_center" hgap="8" vgap="8"
                  style="-fx-padding: 40 0 0 0">
            <children>
                <Label text="Sign In"
                       style="-fx-font: NORMAL 14 Tahoma;"
                       GridPane.columnIndex="0" GridPane.rowIndex="0"/>

                <Label text="Username:"
                       GridPane.columnIndex="0" GridPane.rowIndex="1"
                       labelFor="$usernameField"/>
                <TextField fx:id="usernameField" prefColumnCount="10"
                           GridPane.columnIndex="1" GridPane.rowIndex="1"
                           onAction="#login"/>

                <Label text="Password:"
                       GridPane.columnIndex="0" GridPane.rowIndex="2"
                       labelFor="$passwordField"/>
                <PasswordField fx:id="passwordField" prefColumnCount="10"
                               GridPane.columnIndex="1" GridPane.rowIndex="2"
                               onAction="#login"/>

                <Button fx:id="submitButton" text="Login"
                        GridPane.columnIndex="1" GridPane.rowIndex="3"
                        onAction="#login"/>

                <Label fx:id="statusText"
                       GridPane.columnIndex="0" GridPane.rowIndex="4" GridPane.columnSpan="2"
                       style="-fx-text-fill: #ff0000;"/>
            </children>
        </GridPane>
    </center>

</BorderPane>

Next a Presenter for the above FXML. This handles the login button click (and enter key) to call onto our SecurityService with the username and password. The only slight difference to our other server calls is that we actually handle login exceptions, in particular the BadCredentialsException, and tell the user what went wrong (we should really be doing better error handling everywhere, but that’s another post!).

LoginPresenter.java

package com.zenjava.firstcontact.gui.login;

import com.zenjava.firstcontact.gui.main.MainPresenter;
import com.zenjava.firstcontact.service.SecurityService;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;

import javax.inject.Inject;

public class LoginPresenter
{
    private static final Logger log = LoggerFactory.getLogger(LoginPresenter.class);

    @FXML private Node root;
    @FXML private TextField usernameField;
    @FXML private PasswordField passwordField;
    @FXML private Label statusText;

    @Inject private SecurityService securityService;
    @Inject private MainPresenter mainPresenter;

    public Node getView()
    {
        return root;
    }

    public void login(ActionEvent event)
    {
        statusText.setText(null);

        final String username = usernameField.getText();
        final String password = passwordField.getText();

        final Task loginTask = new Task()
        {
            protected Void call() throws Exception
            {
                // never log password information!
                log.info("Logging in as user '{}'", username);
                securityService.login(username, password);
                return null;
            }
        };

        loginTask.stateProperty().addListener(new ChangeListener()
        {
            public void changed(ObservableValue source, Worker.State oldState, Worker.State newState)
            {
                if (newState.equals(Worker.State.SUCCEEDED))
                {
                    log.info("Successfully logged in as user '{}'", username);
                    mainPresenter.showSearchContacts();
                }
                else if (newState.equals(Worker.State.FAILED))
                {
                    Throwable exception = loginTask.getException();
                    if (exception instanceof BadCredentialsException)
                    {
                        log.debug("Invalid login attempt");
                        statusText.setText("Invalid username or password");
                    }
                    else
                    {
                        log.error("Login failed", exception);
                        statusText.setText("Login error: " + exception);
                    }
                }
            }
        });

        new Thread(loginTask).start();
    }
}

We need to load this Presenter and FXML in our FirstContactAppFactory:

@Bean
public LoginPresenter loginPresenter()
{
    return loadPresenter("/fxml/Login.fxml");
}

And we need to sort out our MainPresenter so it can show the Login page when requested to do so:

public class MainPresenter
{
    ...

    @Inject private LoginPresenter loginPresenter;

    public void showLogin()
    {
        log.info("Showing Login page");
        contentArea.setCenter(loginPresenter.getView());
    }

    ...
}

Finally we want to show our Login page on startup, so we make a minor tweak to FirstContactApp:

...
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FirstContactAppFactory.class);
MainPresenter mainPresenter = context.getBean(MainPresenter.class);
mainPresenter.showLogin();
...

Hey presto! We now have a secured client-server application with Login screen.

Step 6: Try it out

Start your server now, and then run an instance of the client. You should be prompted with the following login screen:

Try entering an invalid username and password and check that you get a suitable error message. Next try logging with username ‘user’ and password ‘user’. You should be taken to the normal search screen, which you have permission to access. If you edit or add a Contact however, you will see an AccessDeniedException in the stack trace since you only have the ‘USER’ role, which does not have permission to edit data (our error handling is currently very poor, but you can fix this easily – I’ll probably look at doing so in a future post).

Run another instance of the client and this time login with username ‘admin’ and password ‘admin’. Now try editing Contact information. Since the ‘admin’ user has the ‘ADMIN’ role you can now do everything without any error.

As mentioned, this is a very basic Security setup, just to give you the idea and provide a base to work off. We’re missing simple features like ‘logout’ and we don’t actually associate Contacts with the logged in user when they are created so anything one user creates, the other can see and vice-versa. Additionally our users are all stored in an XML config file, which is not at all flexible and our passwords are stored in plain text, meaning anyone with access to the XML file can read them.

Spring Security provides the means to improve on all of this, and if you’re in a hurry, you can hunt through the Spring Security documentation to work out how. I’ll do my best to add the features most proper JEE applications need over the coming months as time permits, but if you have specific areas that you’d like to see fleshed out, post questions below.

The full source code for this post can be found at: http://code.google.com/p/jfxee/source/browse/trunk/first-contact/first-contact-security/

Continue Reading→
    • Mar
    • 23
    • 2012

Captains Log

Posted by In Uncategorized

This entry is part 14 of 15 in the series Building JEE applications in JavaFX 2.0

It’s not galmourous or exciting, but good logging is critical to the long term survival of any large application. Logging in Java has a bit of a sordid history, with a few bad decisions made in the early days that still cause some lingering hassles. More recent frameworks have patched over some of this mess but there’s still a few hoops we have to jump through to setup simple logging.

Since JavaFX 2 uses standard Java we have all the same logging options (and hassles) as with any normal Java application. In this post we’re going to look at integrating goold old Log4J into our First Contact application. We’re also going to make use of SLF4J, which is a generic logging API that sits ontop of Log4J. Using this generic API we can keep our code free from specific Log4J dependencies, which allows us to change between any of the supported logging frameworks simply by changing configuration files (SLF4J also has binding for a number of other logging platforms such as java.util.logging, and Apache Commons Logging).

We’ll add logging support to both our client and our server. We could therefore put all the setup in the common module, so we only need to configure it one place. This is a bit of a coincidental symmetry however – there is no technical reason that the client and server should share the same logging infrastructure, particularly at a configuration level (we often want different logging levels for client and server). As such, I tend to setup logging in the client and server completely independently of each other, giving me the option to change one, should I have need, without impacting the other. If this approach doesn’t sit well with you, feel free to merge all the following setup in the common module.

Step 1: Add the dependencies

Starting with our previous code base once again, the first thing we need to do is include the SLF4J and Log4J dependencies in both our client POM and server POM. The additional dependencies are:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.4</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.4</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
</dependency>

As well as the base libraries for Log4J and SLF4J, we’ve also defined the SLF4J-to-Log4J binding. Adding this JAR is all the configuration we need to make SLF4J use Log4J as its implementation – SLF4J detects the available binding at startup and uses it automatically.

Step 2: Add log calls to our code

To log messages from within our code, we need to define Loggers. Typically you define one per class that you are logging from, and define the Logger as a static instance in that class. So for example if we want to log from within the ContactSearchPresenter class we do the following:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ContactSearchPresenter implements Initializable
{
    private static final Logger log = LoggerFactory.getLogger(ContactSearchPresenter.class);

    ...
}

And in the ContactDetailPresenter we do the following:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ContactDetailPresenter
{
    private static final Logger log = LoggerFactory.getLogger(ContactDetailPresenter.class);

    ...
}

Once we have these Loggers setup we can then use them along the lines of the following:

public void search(ActionEvent event)
{
    String searchPhrase = searchField.getText();
    final String[] keywords = searchPhrase != null ? searchPhrase.split("\\s+") : null;
    final Task<List<Contact>> searchTask = new Task<List<Contact>>()
    {
        protected List<Contact> call() throws Exception
        {
            log.info("Searching for contacts, keywords = {}", keywords);
            return contactService.searchContacts(keywords);
        }
    };

    searchTask.stateProperty().addListener(new ChangeListener<Worker.State>()
    {
        public void changed(ObservableValue<? extends Worker.State> source, Worker.State oldState, Worker.State newState)
        {
            if (newState.equals(Worker.State.SUCCEEDED))
            {
                List<Contact> matches = searchTask.getValue();
                log.info("Found {} matching contacts", matches.size());
                resultsList.getItems().setAll(matches);
            }
            else if (newState.equals(Worker.State.FAILED))
            {
                Throwable exception = searchTask.getException();
                log.error("Contact search failed", exception);
            }
        }
    });

    new Thread(searchTask).start();
}

SLF4J supports token replacement in the search phrases. So if you call something like:

log.info("value1 = {}, value2 = {}", value1, value2);

then each of the ‘{}’ elements will be replaced by the value passed in. For backwards compatability reasons they’ve not used varags so if you want to do more than two parameters you have to create an array, so something like:

log.info("value1 = {}, value2 = {}, value3 = {}", new Object[] {value1, value2, value3});

When you log an ‘error’, you can pass an exception as the parameter instead of the value replacements. In this case, the full stack trace of the exception will be logged along with the message you provide.

See the SLF4J documentation for more information on all of this.

Choosing how, when and where you log is an architectural decision that should be based on your particular needs (i.e. the complexity of your application, the usage of it, etc). The main levels available are ‘error’, ‘warn’, ‘info’, ‘debug’ and ‘trace’.

‘Error’ and ‘warn’ are pretty self explanatory. I tend to use ‘info’ to track the high-level user flow (e.g. “the user is now adding a new contact called John Smith”) so that this log can be used to trace back what the user was doing (or trying to do). I then use ‘debug’ for finer-grained, processing details (e.g. “Contact John Smith was created with ID 31″). Occasionally I will use ‘trace’ for very low level debugging, such as when iterating through a a loop with complex logic.

In the server, I tend to log at least one ‘info’ message in every service call. In the client, I tend to log an ‘info’ message at least once per Presenter method. I then generally add ‘debug’ logging in a more add-hoc fashion in the places where I think it will be useful. I generally don’t do a lot of logging inside the view (since you can usually see this stuff anyway), unless the view logic is doing something a little complex like dynamically loading a sub-view based on state, etc.

It’s considered good practice to also enclose your logging in an ‘if’ check to make sure the level of logging is enabled. So for a debug call you should do something like the following:

if (log.isDebugEnabled())
{
    log.debug("Some log message");
}

This stops unnecessary logging calls from happening and can improve performance. The check is not required though. If you don’t do it, your messages will still get filtered if the corresponding log level is not enabled. The check just stops an extra method call (and its parameters) getting added to the stack and any avoids any processing you do in building your log message, etc.

I tend to add this check only on log calls that either get called a lot (e.g. the body of a loop), or that have a lot of data to be logged (e.g. an array of values or a large object tree). Typically I don’t bother with this check for ‘error’ or ‘warn’ since they happen so infrequently, and I rarely bother with ‘info’ calls as well. Debug calls I usually do the check, and for trace calls I pretty much always use the check.

All of the above are just tips and suggestions. Logging is more a gut feel than a precise science. Define your own logging strategy to suit your needs – think about what you as a developer coming back to fix a bug in this code in three years time would want to see in the log file.

Step 3: Configure your loggers

Finally, we need to define a Log4J configuration file to format the log about and restrict the levels of log messages we are going to include in our log file. For this we simply add a file called ‘log4j.xml’ to src/main/resources of both the client and server modules.

src/main/resources/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%-5p %c{1}] - %m%n"/>
        </layout>
    </appender>

    <logger name="com.zenjava">
        <level value="debug"/>
    </logger>

    <root>
        <priority value ="info" />
        <appender-ref ref="console" />
    </root>

</log4j:configuration>

We’ve defined a single ‘appender’, which just logs to the console, and then we’ve set the root level of logging to ‘info’, which means only messages of ‘info’ and above will get logged. For our code however, we want to know more of what’s going on, so we’ve defined an additional logger for everything under ‘com.zenjava’ to show messages of ‘debug’ or above.

This configuration is very flexible, for a production environment you will likely want a rolling file appender and will also want to add different loggers and levels (e.g. you might want to enable Hibernate’s SQL logging to track database calls). You’ll find plenty of info on configuring Log4J for your specific needs on the web.

The full source code for this blog can be found at: http://code.google.com/p/jfxee/source/browse/trunk/first-contact/first-contact-logging/

Continue Reading→