RSS feed
Home | October 2007 >>

Packaging Java apps for OS X with Maven

Anyone attending a Java conference the past few years must have noticed the steady increase of Java developers working on a Mac. The good looking hardware sure helps, but the main reason people are flocking to the Mac is OS X.

Apple's operating system is a great platform for Java developers since it combines the power of Unix with a UI that is actually possible to use. Installing an application is as easy as downloading it and dragging it to the Applications folder.

But that's until you try to install Java software. Some projects are helpful and create double-clickable jars, others just reuse the scripts from their Linux distribution and force you to start Terminal just to lauch their app.

The reason for this is probably that hand-crafting Java application bundles for OS X is quite tedious. A while back I decided to do something about this sad state and make the Java world a better place to be for Mac people.

What I did was to create a Maven plugin that will take any Maven project  and make it into a first class OS X application bundle.

But first, a little motivational example to illustrate why you should care about this:

My friend Sigurd has made this cool little Java app called "Hva" that helps you keep track of hours. Every now and then it will pop up and ask you what you've done  since last time.



(For those of you who's Norwegian is a little rusty: "Hva" translates to "What" and this dialog asks what you've been up to since 7:36 PM)

Because Sigurd is one of these helpful guys, he distributes his app as a double-clickable jar file.

Like any other application, I dragged the jar file to the Applications folder on my Mac and this is what it looks like:



This really isn't too bad, but there are some subtle things that tells me this is not a properly bundled OS X application. First "Hva?" is the only application that has a file name suffix (".jar") in it's name.  Second, the logo of the application is the generic and dull Java Application logo.

So what can Sigurd do to give the users of Hva a better experience on the Mac?

Well, if he's using Maven (and he should!) there is no longer any excuse for giving his Mac users a bellow par experience of his application:

Yesterday I released the OS X Application Bundle Maven plugin which is a part of the Mojo project over at Codehaus.

All you need to give this plugin is  a main method to launch and it creates a beautiful OS X application bundle ready for clicking by your users.

In order to use this plugin for Hva, Sigurd could add hva.jar to his Maven repository and create a Maven project with this pom.xml:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>hva</groupId>
<artifactId>hva-maven</artifactId>
<version>1.0-SNAPSHOT</version>
<name>hva</name>
<dependencies>
<dependency>
<groupId>hva</groupId>
<artifactId>hva</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>

Then he needs to add the following plugin configuration:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>osxappbundle-maven-plugin</artifactId>
<version>1.0-alpha-1</version>
<configuration>
<mainClass>Hva</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>bundle</goal>
</goals>
</execution>
</executions>
</plugin>

Running mvn:install should now create an OS X application bundle in the project's  target directory:


As you can see, the plugin has created an application bundle, plus packaged the application in both a zip and a disk image file ready for distribution.

This is nice, but what if Sigurd wants to add a custom icon to Hva? First he needs to open the Icon Composer utility located in /Developer/Application/Utilities/Icon Composer:


 

Here, I've created an icon in various sizes from the photo in my collection that looked most like the letter "H". I've stored this file as "hva.icns" in src/main/app-resources and used the iconFile property to tell the plugin where it can find the icon, like this:

<plugin>
<groupid>org.codehaus.mojo</groupid>
<artifactid>osxappbundle-maven-plugin</artifactid>
<version>1.0-alpha-1</version>
<configuration>
<mainclass>Hva</mainclass>
<iconFile>${basedir}/src/main/app-resources/hva.icns</iconFile>
</configuration>
<executions>
<execution>
<goals>
<goal>bundle</goal>
</goals>
</execution>
</executions>
</plugin>

After running mvn:install, our target/ directory now looks like this:



Let's rename it and move it to the Applications folder:


Notice the neat icon on the application and how much smoother it looks in the Dock now:


 

See the plugin's site for full documentation. The Maven example project I created for Hva can be found in the Simplericty Subversion repository, here:

http://simplericity.org/svn/simplericity/projects/hva-maven/


 


Better webapp config: Spring integration and an example


I promised following up my post about a better way to configure Java web applications with an example. The key point to take home from that post was that:

We should move all configuration out of the [Java] webapp, treat configuration as just another type of data and stick it in a directory that we point our webapp at.

Now for the example: There are two things you need to set up.The first part is to configure your application so that it will be able to find it's data directory. The second part is to configure your application server to point at the correct directory.

Preparing your app

If your application is using Spring all you need to do is to add the following line of XML to your bean context:

<bean class="com.simplericity.datadirlocator.spring.DataDirectoryPropertyReplacer"/>

That's all there is to it. The DataDirectoryPropertyReplacer will  configure a DefaultDataDirectoryLocator and replace any references to ${dataDir} in your Spring context with whatever is returned by the locateDataDirectory() method. Lets say you  have an UploadFileController and it needs to  know where to put  uploaded files.  Then configure your controller like this:

<bean class="com.example.UploadController" id="uploadController">
 <property name="uploadDirectory" value="${dataDir}/uploads"/>
</bean>

The defaults should work fine in most cases, but there are quite a few options if you'd like a more customized soluton. The first thing to do is to configure a custom DefaultDataDirectoryLocator:

<bean class="com.simplericity.datadirlocator.spring.DataDirectoryPropertyReplacer">
<bean id="dataDirLocator" class="com.simplericity.datadirlocator.spring.ServletContextAwareDataDirectoryLocator">
<property name="defaultDir" value="$HOME/.myappdata"/>
</bean>
</bean>

This example configures the fallback data directory to be the .myappdata directory inside the user's home directory. There are other properties you can set to override the various property names the locator will look for. See the source Javadocs
for full details.

Configuring your app server

There seems to be as many ways to do this as there are app servers, so I'll show you how to do it on Jetty and Tomcat and leave the rest as an exercise.

Using a JDNI property on Jetty

Add this to your Jetty configuration:

<New id="dataDir"  class="org.mortbay.jetty.plus.naming.EnvEntry">
    <Arg>dataDirectory</Arg>
    <Arg type="java.lang.String">/var/local/myapp/</Arg
    <Arg type="boolean">true</Arg>
</New>

Using a JNDI propery on Tomcat:

Add this inside your webapp's Context tag:

<Environment name="dataDirectory" value="/var/local/myapp"
      type="java.lang.String" override="true"/>


Using a Context param on Jetty:

Add this to your jetty-web.xml:

<Set name="initParams">
    <New class="java.util.HashMap">
        <Put name="dataDirectory">/var/local/myapp/</Put>
    </New>
</Set>


Setting a system property
This should be easy to do on any app server using the -DdataDirectory=/var/local/myapp command line switch.


Adding property file to the classpath

Add a file called myapp.properties to the WEB-INF/classes directory containing the following property:

dataDir=/var/local/myapp

There is a complete webapp example in the Simplericity Subversion repository.
Tags :

A better way to configure Java Web Applications

Anyone who's installed a few Java EE webapps in their life knows what pain it can be to establish and maintain a working configuration. It's usually far from the it just works experience I've gotten used to and tends to involve mocking around  quite a few files.

Sure, most applications these days do their best in reducing the number of settings you need to grok in order to get started.  Some even provide nice setup wizards that lets you do the configuration in the browser the first time you use the application.

But these startup wizards tend to be just that - for startup. As soon as you need to do something a bit off-track, the convention seems to be to fall back on tweaking a file somewhere inside the WEB-INF folder.

Atlassian, the makers of great software, have made a really nice setup interface for their Jira product. They also bundle Tomcat with their applications so all you need to do is to unzip, start Tomcat, fire up you browser and boom! you're ready. It's almost to good to be true. Or actually, it is.

Try do something that's a bit more complicated, like integrating with LDAP or connecting Jira to MySQL and you'll soon find yourself reading about how to «edit the field-type-name attribute in the atlassian-jira/WEB-INF/classes/entityengine.xml file».

Another example is Pebble, the blogging software powering this site. The reason I chose Pebble was because of its «unrivalled ease of installation and use».  But what what if you want to use the multi-blog feature of Pebble? You guessed it, it's done by «changing the multiBlog property in the /WEB-INF/applicationContext-pebble.xml file to true».

(For the record: Atlassian is a truly amazing company and I think they have some top-notch products, what I'm trying to get across here is that parts of their configuration system deserve some amazification.)

So what's the problem?

My main problem with this isn't that there are lot of settings to tweak, but that the configuration files are mixed with the application files inside the WEB-INF directory. You might ask what the problem is with keeping configuration within WEB-INF. The configuration have to reside somewhere right? There are a few reasons this might not be a good idea:

It complicates backup.
As soon as you modify a war, you'll have to back up your changes. In the event of a disaster your users can't simply replace the war with a fresh one since that will kill their configuration tweaks.

It makes software updates a pain.
Consider what happens when you release a new version of your webapp. Your users will typically download the new war file, (hopefully) backup the old one, and then they have to remember and manually merge the all the settings they had in the old install. Anyone who has ever had the responsibility of keeping a mildly customized version of Jira up to date will know the amount of misery this can cause.

It breaks Inversion of Control.
Inversion of Control is about injecting dependencies to a component from outside instead of keeping them inside it. I don't see any reason this shouldn't also be applied at the webapp level. Your webapp is the component and its configuration is a dependency, hence it should be  injected from outside.

It requires write access to the WEB-INF dir.
This isn't necessarily a problem, but it will be if you decide not to unpack your war, if you're trying to tighten security in the file system or you're running from a read-only medium.

It makes throwaway wars impossible
Some times it makes sense to unpack the war to a temporary directory every time you start your application server and then just delete it on shutdown. If you want to bundle Jetty and your webapp in a single, double-clickable jar, this would be a requirement.

How can we solve this?

This post would be just a rant If I didn't back it up with a proposed solution and some code. The one-sentence version of my proposal is that:

We should move all configuration out of the webapp, treat configuration as just another type of data and stick it in a directory that we point our webapp at.

The first part of this proposal is the easy one. Instead of putting configuration files in WEB-INF, put it in some other directory outside your war and application server. This should also be the directory where you put indexes, database files or any other data that your application produces or consumes.

The second part is about pointing your webapp at the data directory. (Remember that configuration is just another type of data). But how will the application know where to look for it's data directory? There are several ways we could implement this:

Setting a system property.
We'll start out application server with a -DdataDirectory=/var/local/application/data flag. The problem with this is that we can only run one instance of an application because the system property can only have a single value.

Setting a JNDI property
Instead of reading a system property, the application can read a JNDI environment property. This enables multiple instances of the application to be run at the expense of more configuration in your application server.

Setting a context parameter
This is quite similar to the JNDI method but we instead configure the application server to set a context parameter. Your application will see this as if it had a context-param set in the web.xml file.

Adding a property file to the classpath
This is a bad solution since it breaks our rule about not writing to the war. Still it could be useful for people who for some reason can't use one of the preferred methods.

Just go for a sensible default
This is the convention over configuration approach. Instead of configuring the data directory, just select a sensible default, typically a directory named like the application in the user's home directory. In order for this to work your application will have to know how create and startup from this empty directory, but that's something it should be able to do anyway. Like the system property solution, this one will only allow once instance of the application on the same system.

The implementation

Checking all these different types of configuration adds quite a bit of complexity to our application. Luckily, Java has this nice thing called interfaces that we can hide all the complexity behind. I've called this interface DataDirectoryLocator and it's dead easy:

/**
* Component responsible for locating a data directory based on some strategy.
* Typically used to cleanly separate configuration and data from a web app.
*/
public interface DataDirectoryLocator {
File locateDataDirectory();
}

Calling the locateDataDirectory() metod is all your application needs to do in order to figure out where to read it's configuration from.

The dirty details of how to find the directory is implemented by the DefaultDataDirectoryLocator. It will check the different configuration options in the following order:

  1. JNDI environment property
  2. Context parameter
  3. System property
  4. Classpath property file

It will pick the first one set, and if none of are set, the configured default location is used.

If you would like to use this code your own projects, feel free to do so by checking out the sources from the Simplericity Subversion repository and build it by running mvn install.

I'll be following up this post  with an example of how to use and configure the data directory locator.
Tags :