When it comes to generating command-line scripts for Java applications, Maven "appassembler" plugin comes handy. Its "assemble" goal does all the maven magic, i.e. searching the dependencies used for creating the Java application, adding them into the classpath of resultant script and finally copying all relevant jars to a single place. It was all working very nicely until I stumbled across the problem of long classpaths in the Windows OS.

Irrespective of whether you use DOS prompt or cygwin, Windows limits the length of environment variables. Though there are various options to overcome this problem using Java 6 wildcard classpath, mapping the path to some drive etc, they all look like workarounds to me as problem can recur again anytime.

After some amount of research, I found appassembler plugin resolves this issue with booter-windows and booter-unix daemons. From the outset it looked like daemon does something else as the name is a bit misleading but in reality it's a generic way to start your Java applications. The booter-unix/booter-windows platforms were introduced for the sole purpose of resolving the long classpath issue (see MAPPASM-43) in appassembler maven plugin. Here's how it looks like in the pom you are working with:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <configuration>
        <repositoryLayout>flat</repositoryLayout>
        <installArtifacts>false</installArtifacts>
        <target>${project.build.directory}/appassembler</target>
        <defaultJvmSettings>
            <initialMemorySize>512M</initialMemorySize>
            <maxMemorySize>1024M</maxMemorySize>
            <extraArguments>
                <extraArgument>-DconfigFile=../../etc/config.properties</extraArgument>
                <extraArgument>-Dlog4j.configuration=../../etc/log4j.properties</extraArgument>
            </extraArguments>
        </defaultJvmSettings>
        <configurationDirectory>etc</configurationDirectory>
        <daemons>
            <daemon>
                <id>applicationName</id>
                <mainClass>com.xebia.appassebler.sample.Main</mainClass>
                <platforms>
                    <platform>booter-unix</platform>
                    <platform>booter-windows</platform>
                </platforms>
                <environmentSetupFileName>app-env</environmentSetupFileName>
            </daemon>
        </daemons>
    </configuration>
    <executions>
        <execution>
            < phase>package</phase>
            <goals>
                 <goal>generate-daemons</goal>
                 <goal>create-repository</goal>
            </goals>
        </execution>
    </executions>
</plugin>

If you look at the code carefully, you'll find "app-env" as a parameter to "environmentSetupFileName" tag. It is quite useful in passing environment variables to the resultant script. Using app-env (for UNIX) and app-env.bat (for Windows) environment setup files, I could pass a modified $REPO environment variable so that the copied repository containing dependencies is shared between booter-unix and booter-windows folders as follows:

|-- booter-unix

|   |-- bin

|   |   |-- applicationName
|   |   `-- app-env

|   `-- etc

|       `-- applicationName.xml

|-- booter-windows

|   |-- bin

|   |   |-- app-env.bat

|   |   `-- applicationName.bat

|   `-- etc

|       `-- applicationName.xml

|-- etc

|   |-- config.properties

|   `-- log4j.properties

`-- repo

    |-- <dependency1>.jar

    |-- <dependency2>.jar

    |-- ...

To copy the environment files and result of appassembler:generate-daemons maven goal to the appropriate locations we can use "maven-assembly-plugin" as follows:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptor>src/main/conf/descriptor.xml</descriptor>
    </configuration>
    <executions>
       <execution>
          <goals>
              <goal>single</goal>
          </goals>
          <phase>package</phase>
       </execution>
    </executions>
</plugin>

Here's what src/main/conf/assembly.xml looks like:

<assembly>
    <id>bin</id>
    <formats>
        <format>tar.gz</format>
        <format>zip</format>
        <format>dir</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>target/appassembler/booter-unix</directory>
            <outputDirectory>/booter-unix</outputDirectory>
            <excludes>
                <exclude>*.bat</exclude>
                <exclude>*.cmd</exclude>
            </excludes>
            <lineEnding>unix</lineEnding>
            <fileMode>0744</fileMode>
        </fileSet>
        <fileSet>
            <directory>target/appassembler/booter-windows</directory>
            <outputDirectory>/booter-windows</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>target/appassembler/repo</directory>
            <outputDirectory>/repo</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>src/main/conf</directory>
            <includes>
                <include>app-env.bat</include>
            </includes>
            <outputDirectory>/booter-windows/bin</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>src/main/conf</directory>
            <includes>
                <include>app-env</include>
            </includes>
            <outputDirectory>/booter-unix/bin</outputDirectory>
            <lineEnding>unix</lineEnding>
        </fileSet>
    </fileSets>
</assembly>

Now, if you run "mvn clean package", it provides you the structure as mentioned in listing 2. You can execute the script from booter-windows/bin/applicationName.bat on Windows without worrying about long classpaths now.