Saturday, October 31, 2015

Getting HotswapAgent To Work With Gradle & Netbeans

So I am slightly jealous of  some of the other coder-monkeys I work with:  they take advantage of jrebel to reduce turn around times on webapp work.

For unknown reasons I have never really embraced jrebel.   It is hard to explain why, it seems like a good tool, but I just won't use it.

At any rate, for the uber tedious tasks of formatting HTML views and rough out work of java controllers, a short cut around the full redeploy would greatly improve my through put.

HotswapAgent claims to be an OSS and free solution for hot class updating comparable to jrebel.   I decided to give it a go.

My Parameters

So I don't like my development models and procedures to deviate too much from the official build practices and procedures established for the projects I am working on.  Gradle is my LCD for build and project management.   The rest of the parameters of the experiment follows:
  • JDK 1.8 u 66
  • JBOSS EAP 6.x or Wildfly 8.x or greater (I opted for WF 9.0. as my test project for the exercise would table into ee7; for the Hotswap agent part, I don't think JBOSS vs WF matters). 
  • Gradle, of course; I chose 2.8
  • Hotswap Agent 0.3 - Stable (& DCEVM-light-8u5, more about that latter). 
  • Netbeans 8.1rc2  and gradle support plugin version  1.3.7.2 (but earlier versions will probably work).  
Notice that most of the ingredients above are fairly bleeding edge.   I do think I can ratchet back to more main stream releases of the above stuff and this process should still work.  Oh and it probably doesn't matter a ton, but fwiw; I was doing this on my laptop running Fedora Core 22 (x64).  Finally, it helps to be familiar with gradle and the gradle support in NB. 

Installing HotswapAgent

Go here and follow the directions.  Now, you'll see that one of the first steps is to instrument your jvm with DCEVM.   Further you'll notice that I threw caution to the wind and used the u51 installer on u66.  While this proved to be a working combo (in my case), I had issues (unrelated to the jdk mismatch). 

I don't know why I continue to do this, but 9 times out of 10, while following some recipe from the interwebs (like the HSA quick start directions), I take some short-cut or liberties with the instructions, as if I know better than the instructions. DO NOT DO THIS! It almost always back-fires. Follow the directions or recipe meticulously and reproduce the documented product first. Only after reproducing the results as per the instructions, should you begin your own hacking.

I stuffed the hotswap-agent.jar in a directory called  /opt/HotswapAgent-0.3.    In the "Run your Application" section, the quick start says to start your application the following command line additions:

-XXaltjvm=dcevm -javaagent:PATH_TO_AGENT\hotswap-agent.jar


This translated to:

-XXaltjvm=dcevm -javaagent:/opt/HotswapAgent-0.3/hotswap-agent.jar

I chose to put it in the server properties dialog as suggested here.  It looks like this:



Now starting WL 9.0.1 from NB will output stuff like this:

HOTSWAP AGENT: 17:58:59.760 INFO (org.hotswap.agent.HotswapAgent) - Loading Hotswap agent {0.3.0-SNAPSHOT} - unlimited runtime class redefinition.
HOTSWAP AGENT: 17:59:0.237 INFO (org.hotswap.agent.config.PluginRegistry) - Discovered plugins: [Hotswapper, WatchResources, AnonymousClassPatch, Hibernate, Spring, Jersey2, Jetty, Tomcat, ZK, Logback, JSF, Seam, ELResolver, OsgiEquinox, Proxy, WebObjects, Weld]

These lines will be towards the top of the server output.  There are a couple of other ways to nail in these settings:  hack the standalone.conf (or other suitable conf) or perhaps set the environment variable JAVA_OPTS.

The Gradle Bits

So I will spare all the gory details of how I got to it,  and just present the gradle file that proved to be a working solution for my simple tests.   Some discussion will follow the example:

apply plugin: 'war'

sourceCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax:javaee-api:7.0'
    
    testCompile group: 'junit', name: 'junit', version: '4.10'
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.8'
}

ext.deployBase="/opt/wildfly-9.0.1.Final/standalone/deployments"
ext.deployTarget="$deployBase/$war.archiveName"
ext.dodeployLCFname = "$deployTarget" + ".dodeploy"
ext.deployedLCFname="$deployTarget" + ".deployed"
ext.undeployedLCFname="$deployTarget" + ".undeployed"


task updateResources(type: Copy) {
    from 'src/main/webapp'
    into deployTarget
}

task deployWar(type: Copy){
    
    into deployTarget
    with war
    
    doLast{
        def f = file(dodeployLCFname)
        f.lastModified = System.currentTimeMillis()
        f.createNewFile()        
    }
}

task undeployWar <<{
    delete deployedLCFname
    delete deployTarget
    delete undeployedLCFname
}

Basically this gradle has tasks to (in addition to the normal stuff):

  • deploy an exploded war into the wildfly deployments directory, including the creation of a deployment  life cycle marker file that tells WF to deploy the app in the .war directory.  Pro Tip:  The deploy scanner needs to be turned on (usually defaults to on) and more info about the life cycle marker files is here.
  • undeploy will drop the needed marker file and clean up the exploded war area. 
  • updateResources refreshes the files from the webapp source directory to the exploded war area (more on this later).

How To Use

Mostly follow the directions on the quick start.  Here are the steps using NB actions, menus, or buttons: 


1)  Start WF in debug mode:
2)  Clean and build the WAR project:

3)  Deploy the WAR:
4)  Test your app  to get a base line.
5)  Attach NB Debugger to running WF instance:
6)  Make a change to java file (presumably a file that will noticeably affect the test) and save it.
7)  Use the NB Debugger apply changes:
8)  Retest and observe changes

What About HTML, JSF, or JSP Pages or Other Resources

The non .java things should be hot swappable too.  I had to spend a bit of time hunting this down. HSA has a configuration properties file called hostswap-agent.properties that is supposed to help control a few things.  What the NV pairs do and how to apply them took some work.

For starters, "it [hostswap-agent.properties] goes on your classpath" isn't really helpful.  That statement can mean a bunch of things with the complexities of modern EE servers.   Trial and error lead me to the fact that for a war,  /WEB-INF/classes will work.

Early on in my experiment I was using native NB projects (ANT files) to get this working.    The HSA property 'extraWebappContext' worked for activating updates to resources being immediately available to WF without any extra steps.    I couldn't repeat that result for gradle projects; and thus the task updateResources was needed.

In my sample, I was using JSF so after I touched an facelet (xhtml file), I had to:
and then changes to my resources 'took'.

Areas For Improvement

It is not perfect, but it works and reduces the need to redeploy frequently.   Here are some things that could use some attention and I hope to continue to tinker with:
  • The G4NB plugin allows some customizations of IDE actions to the corresponding gradle  tasks that will be invoked.   Apply code changes could trigger 'updateResources', making the process of hot applying of java and others resources, the same. 
  • The hotswap-agent.properties may still be tunnable so that updateResources isn't needed for stuff like JSF files. 
  • Perhaps there is a way to chain the G4NB IDE action 'build' to invoke 'apply code changes'?
  • updateResources copies all files, regardless of changes; perhaps, there is a way to only refresh files affected by most recent save? 
  • HSA has support for 'autoHowSwap'.   This seems like it gets rid of the need to 'Apply Code Changes' and having a debugger attached to target vm.  If this is the case, then some simple but slick combination of  G4NB IDE action tweaks and gradle tasking could further eliminate some clicks or needed interactions. 
  • What about EARs?  Or wars, with some jars or ejb-jars that need swapping?   HSA has devices for this; but some technical investigation is needed to see how this recipe can include these things. 
  • So a simple gradle task dependency, like deployWar.dependsOn war, would take away the build step. 
  • The gradle stuff could be wrapped up in a plugin or the like, and indeed my 'flyboss' plugin has a stub for exploded deployments that I haven't finished yet.   

Project Structure

This sample was exceedingly simple and followed normal  maven/gradle source code organization.   Here is a quick picture of the file organization:


No comments:

Post a Comment