Tuesday, June 23, 2009

Starting Terracotta Server as a Windows service

There's an easy way to set up Terracotta server as Windows service using the open source Java Service Wrapper.

I've used their Integration Method 1, which uses a wrapper WrapperSimpleApp to run Terracotta server main class com.tc.server.TCServerMain.

First, just download and install the latest version wrapper-windows-x86-32-3.3.5. Then make copy of conf/wrapper.conf and name it TerracottaServer.conf.

Fill out these needed properties:

# for logging
wrapper.java.command.loglevel=INFO

# Method 1 main class
wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp

# classpath
# (1) is for WrapperSimpleApp
# (2) is for Terracotta Server - You'll need to fix up the path for your own use
wrapper.java.classpath.1=../lib/wrapper.jar
wrapper.java.classpath.2=d:/work/builds/terracotta-3.0.1/lib/tc.jar

# Java Additional Parameters
## NOTE: -server option only works with a JDK, not with JRE
wrapper.java.additional.1=-server
wrapper.java.additional.2=-XX:+HeapDumpOnOutOfMemoryError
wrapper.java.additional.3=-Dcom.sun.management.jmxremote
wrapper.java.additional.4=-Dtc.install-root=d:/work/builds/terracotta-3.0.1

# Initial Java Heap Size (in MB)
wrapper.java.initmemory=512

# Maximum Java Heap Size (in MB)
wrapper.java.maxmemory=512

# Application parameters. This is where you specify TC server main class
wrapper.app.parameter.1=com.tc.server.TCServerMain



That's pretty much all you need. There are Batch scripts in the "bin" folder of the wrapper installation where you can install/uninstall your service. Just make sure you modify those scripts to point to TerracottaServer.conf file you made earlier.

Once you have the service install, you can start/stop the service by using Windows services manager or by using the scripts. There will be a log of the run under "/logs"

I've made a copy of the scripts and the TerracottaServer.conf for easy tryout.

Wednesday, June 03, 2009

Executing a Maven plugin from a Maven plugin

I've searched for a way to call a Maven plugin from inside another plugin but couldn't find any. So I've put on my hacker hat and made it happen. It might not be the best way to do it or even orthodox but here goes.

For example, Terracotta has a Maven plugin, namely "tc" and I want to add a 'help' goal to it which works exactly like the help plugin

With the help plugin, you can do this:
mvn help:describe org.terracotta.maven.plugins:tc-maven-plugin -Ddetail


But it's a little verbose. I want to do
 
mvn tc:help


which would accomplish the same thing. To be able to do that, I have my "tc" plugin depended on "help" by declaring it as a dependency in the pom.

    <dependency>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-help-plugin</artifactId>
     <version>2.1</version>
    </dependency> 


Then I looked in the source of the "help" plugin, specifically DescribedMojo.java which handles to goal "help:describe" as shown above. So basically, if I can construct a DescribeMojo instance and fill in the needed info, you'll be able to execute it just like your own mojo. The trick is, DescribeMojo has all of it fields "private" and no setters.
So I hacked the hell out of it and dug into its privates by using reflection. This is a no-no in so many books but I feel I'm working on a known version (2.1) of the "help" plugin so the chance of its API change is slim.

/**
 * Print help for all known goals
 * 
 * @author hhuynh
 * 
 * @goal help
 */
public class HelpMojo extends AbstractMojo {
  .....

  /**
   * @parameter expression="${project.remoteArtifactRepositories}"
   * @required
   * @readonly
   */
  private List remoteRepositories;

  /**
   * The goal you want to see help. By default help prints for all goals
   * 
   * @parameter expression="${goal}"
   */
  private String goal;

  public void execute() throws MojoExecutionException, MojoFailureException {
    DescribeMojo describeMojo = new DescribeMojo();
    setValue(describeMojo, "artifactFactory", artifactFactory);
    setValue(describeMojo, "pluginManager", pluginManager);
    setValue(describeMojo, "projectBuilder", projectBuilder);
    setValue(describeMojo, "project", project);
    setValue(describeMojo, "settings", settings);
    setValue(describeMojo, "session", session);
    setValue(describeMojo, "localRepository", localRepository);
    setValue(describeMojo, "remoteRepositories", remoteRepositories);

    setValue(describeMojo, "plugin", "org.terracotta.maven.plugins:tc-maven-plugin");
    setValue(describeMojo, "detail", true);
    setValue(describeMojo, "goal", goal);
    
    describeMojo.execute();
  }

  private void setValue(Object o, String field, Object value) throws MojoFailureException {
    Class c = o.getClass();
    Field _field;
    try {
      _field = c.getDeclaredField(field);
      _field.setAccessible(true);
      _field.set(o, value);
    } catch (Exception e) {
      throw new MojoFailureException(e.getMessage());
    }
  }
}


The 3 important fields I had to fill out are:

  1. plugin: full name of the plugin you want to print help

  2. detail: I want full description by default

  3. goal: If specified, it only prints help for that goal


There are other component fields that Maven will normally inject them into your own mojo automatically. For example, the field "remoteRepositories" is needed by DescribeMojo and since we construct it by hand, we have to inject the value of this field ourselves. But you can get the value of it for free by Maven so all I need to do is just declaring it and passing it along.

Voila, you got a fully functional DescribeMojo instance and all that left is to call "execute()" on it.

So that's how the hack is done.