Skip to content

Running DHIS 2 with JRebel

Henning Håkonsen edited this page May 19, 2017 · 13 revisions

JRebel allows instant code re-deploy for running Java apps. As DHIS 2 has a substantial compile-and-startup time, this is obviously useful for backend (Java) development.

Due to the nested multi-module structure of the application, however, JRebel is not straightforward to set up with DHIS 2. This is a small tutorial which aims to give an idea of how to set up a basic development environment for DHIS 2 using JRebel, Maven and embedded Jetty, running from the command line.

That is, this can surely be done for other servlet containers (e.g. Tomcat) too, but we focus on mvn jetty:run for simplicity.

Also note that this tutorial uses JRebel as a stand-alone, binding directly to the lib and jar. Most methods rely on IDE plugins to do the hard configuration work, but this turned out to not work well for DHIS 2 (at least for the author of this guide).

What you need

  • The dhis2-core source code
  • A Linux/Unix/macOS environment
  • A JRebel license (for evaluation get a trial or use myJrebel)
  • An intallation of JRebel. Could be stand-alone or could be through an IDE (e.g. IntelliJ JRebel plugin), doesn't matter.

Steps

Generate rebel.xml files

In order for JRebel to interoperate with a maven module a rebel.xml file is needed. Creating these manually is tedious, so we generate them.

DHIS 2 is set up with the rebel-maven-plugin which takes care of this for us. You can run it manually on a module in DHIS 2 like so:

mvn rebel:generate

This quickly gets unmanageable to do for each module, though. Therefore, running the build with the -Pdev flag takes care of running the rebel plugin for you:

mvn install -Pdev

Make sure you do this for the web modules too, so starting from the dhis-2 directory:

mvn install -Pdev && cd dhis-web && mvn install -Pdev

You can validate that the file exists by looking for /target/classes/rebel.xml in any of the modules.

Configure the environment

In order to run mvn jetty with JRebel we need maven itself to run with the JRebel javaagent. There are many ways to go about doing so, and it's probably painless in certain (Linux?) environments (or when running through Eclipse/IntelliJ). In the case of the author, however, it wasn't trivial to figure out (Mac OS X).

The basic requirements are:

  • Setting up the REBEL_BASE, JREBEL_AGENT and JREBEL_LIB environment variables.
  • Ensuring the JRebel javaagent is bootstrapped with Maven when needed (when we want it to)

For the first point the configuration looks like this (modification required for your setup):

JREBEL_AGENT=/path/to/jrebel6/jrebel.jar
JREBEL_LIB=/path/to/jrebel6/lib/libjrebel64.{dylib|so|dll}
REBEL_BASE=/path/to/some/writable/folder # I use ~/.jrebel

The jrebel6 artifacts referenced will be found in your JRebel installation. If you installed using a plugin, for example, it will reside within the plugin installation directory.

Note also that for IntelliJ + Mac the path will contain spaces (because of the pesky Application Support folder). Such paths are not welcome in MAVEN_OPTS, and should be avoided. The author solved this issue by directing through symlinks in /usr/local/bin/xxx.{jar|dylib}.

Now we are, in principle, ready to run mvn with JRebel. To do so:

MAVEN_OPTS="-javaagent:/no/spaces/path/to/jrebel.jar -Xbootclasspath/p:$REBEL_BASE/rebelbase.jar $MAVEN_OPTS"
mvn jetty:run-war # Or whatever mvn target, really

If everything is set up correctly this will run under the JRebel javaagent (you will see a message confirming this).

Now, whenever a class file under target/ is replaced/updated JRebel picks up the changes and reloads the class in the running embedded Jetty instance. Trigger the context reload by performing some action in the app (reload the page, for example) and you will see JRebel reloading the necessary contexts. The reload time is still considerable (20-30 or so seconds on the author's dev machine), but much much faster than re-deploying the application.

Jrebel with IntelliJ

If you are using IntelliJ IDE, we can do this from the IDE. Follow these steps:

  1. Install Jrebel plugin in IntelliJ(Preferences -> Plugins -> Browse repositories -> Jrebel for IntelliJ).
  2. Activate Jrebel(with payed activaion key, trail or activation key from myJrebel).
  3. Activate classes which you want Jrebel to watch. View -> Tool windows -> Jrebel
  4. Run jrel:generate from "Maven Projects" drop out window on the right. It is located in "DHIS Web Portal" -> Plugins -> jrebel -> jrebel:generate. If it does not exist, profiles may be set to "default" instead of dev.
  5. Run install on "DHIS 2" and "DHIS Web Modules Project".
  6. Run "DHIS Web Portal" -> Plugins -> jetty -> "jetty:run-war" (Right click and choose to run with Jrebel)

The Jrebel Execitor should start up. If it does not work check that your environment variables are set correctly.

Fine tuning

One issue with setting the global MAVEN_OPTS like described above is that it affects all runs of mvn within the environment. This isn't really a big deal, but we don't want or need JRebel to run with our compiles, for example. It's only needed when running the application server.

To avoid this we can plug the necessary MAVEN_OPTS in a separate function scope. This has the handy side effect of being a useful shorthand:

function rbl {
  (
    MAVEN_OPTS="-javaagent:/no/spaces/path/to/jrebel.jar -Xbootclasspath/p:$REBEL_BASE/rebelbase.jar $MAVEN_OPTS"
    mvn "$0"
  )
}

Having the rbl function defined allows us to run rbl jetty:run-war -Pdev without modifying the environment, meaning subsequent runs of mvn will run without the JRebel javaagent.

Another point worth mentioning is about the class reloading. It is mentioned above that JRebel detects changes to the target folder. We don't, however, want to deal with manually replacing the class files. Therefore it is wise to set up your IDE to write to target on compiles.

In fact, IntelliJ does this by default for Maven projects, meaning you can change and compile only a single file, which is then picked up by the JRebel javaagent immediately. Neat!