Wednesday, May 4, 2016

Live pre-processing with Maven and Jetty

Running
mvn jetty:run
works well for development as Jetty will reload your web app files (js, css, html, ...) when they change without needing to restart. However handling files that need pre-processed first like .less files is a little more difficult since Jetty can't detect that a specific process in Maven needs re-run.

One solution is to use an external tool to do the pre-processing. As long as the external tool has a file watcher option we can leave it running in the background and then stop it when Maven stops.

<plugin>
<groupId>org.codehaus.groovy.maven</groupId>
<artifactId>gmaven-plugin</artifactId>
<executions>
    <execution>
        <phase>process-resources</phase>
        <goals>
            <goal>execute</goal>
        </goals>
        <configuration>
            <source><![CDATA[
            /*
             * If running in Jetty - pre-process when they change
             * Otherwise just run once
             */
            boolean isJettyRun = false
            List<String> goals = session.getGoals()
            for (String s : goals)
                if (s.equals('jetty:run'))
                    isJettyRun = true

            if (isJettyRun) {
                new ProcessBuilder(["external_app", "--watch_arg", "--watch_std_in"])
                .inheritIO().directory(project.getBasedir()).start()
            } else {
                def proc = new ProcessBuilder(["external_app", "--run_once_arg"])
                .redirectErrorStream(true).directory(project.getBasedir()).start()

                proc.waitForOrKill(15000)
                proc.in.eachLine { line -> println line }
            }
            ]]></source>
        </configuration>
    </execution>
</executions>
</plugin>
Running Groovy inside Maven is a nice quick way to implement a custom plugin. This one is set up to run in the process-resources phase of the Maven life cycle. The first thing we do is determine if we are running with jetty:run or as part of a normal build because we want to handle the situations differently.

When running as part of the normal build process everything is straight forward - build up a process that uses our external tool and pass it whatever arguments it needs to process files once. We also redirect the error stream so we can get errors and output together to display with the rest of the Maven output. We kill the process if it takes longer than 15 seconds and the loop through the output writing it out to the console.

When running with Jetty, our external tool will have to support some special options. It will need to be able to keep itself running and watch for file changes to re-do the pre-processing. It will also need to watch std-in and exit when std-in closes. That last option may sound a little strange as a requirement but there is a reason for it. We could have added shutdown hooks and killed our process when Maven shuts down; but shutdown hooks won't run if Maven is stopped with a SIG_KILL. If you run Maven inside an IDE like IntelliJ, the stop button does a SIG_KILL and will leave your processes running. So to work around this we require our external tool to watch for std-in to close and then when we build our process we inherit the IO of the parent. Now std-in of our process is the same as the std-in of Maven and when Maven shuts down for any reason our process will exit itself.

The next post will build on this and show a complete example for live pre-processing of .less files.