WAR Notes


Hierarchical JARs

It's very conveniently self-documenting to subsume JAR files in a project under subdirectories named for the origin of those JARs. However, ...

Because the Java class loader doesn't search recursively all subdirectories, when you deploy a WAR file with such hierarchically arranged JARs, you will not get them: their symbols will go missing.

  $ jar -xf project.war
  $ tree WEB-INF/lib
  WEB-INF/lib
  |-- ant-contrib
  |   `-- ant-contrib-1.0b3.jar
  |-- ant-tasks
  |   `-- sqlscriptpreprocessor.jar
  |-- apache
  |   |-- commons-collections-3.1.jar
  |   |-- commons-configuration-1.7.jar
  |   |-- commons-lang-2.6.jar
  |   |-- log4j-1.2.16.jar
  |   `-- servlet-api.jar
  |-- gson
  |   `-- gson-1.7.1.jar
  |-- hibernate
  |   |-- antlr-2.7.6.jar
  |   |-- c3p0-0.9.1.jar
  |   |-- hibernate3.jar
  |   |-- hibernate-jpa-2.0-api-1.0.1.Final.jar
  |   |-- javassist-3.12.0.GA.jar
  |   |-- jta-1.1.jar
  |   |-- slf4j-api-1.6.1.jar
  |-- jboss
  |   `-- webplatform-servicelocator-1.0.jar
  |-- jersey
  |   |-- asm-3.1.jar
  |   |-- jersey-client-1.6.jar
  |   |-- jersey-core-1.6.jar
  |   |-- jersey-json-1.6.jar
  |   |-- jersey-server-1.6.jar
  |   |-- oauth-server-1.10.jar
  |   `-- oauth-signature-1.10.jar
  |-- mockito
  |   |-- mockito-all-1.8.5.jar
  |   `-- powermock-mockito-1.4.9-full.jar
  |-- mongo
  |   |-- mongo-2.6.3.jar
  |   `-- morphia-0.99.jar
  |-- mysql
  |   `-- mysql-connector-java-5.1.16-bin.jar
  |-- postgres
  |   `-- postgresql-9.1-901.jdbc4.jar
  `-- spring
      `-- org.springframework.transaction-3.0.6.RELEASE.jar

  12 directories, 30 files

No, this will never work. Instead, you must copy all of the JARs to some flat place and thence include them in the WAR. Here are some targets to put into build.xml to achieve this in bold below. The very cookie jar itself is in green.

    ...
    <property name="deployed_libraries" value="deployed-libraries" />
    ...

    <target name="clean">
        <echo message="==== Cleaning up old targets... ====" />
        ...
        <delete dir="${deployed_libraries}" />   <!-- Remove deployed_libraries subdirectory if there -->
        ...
        <echo message="==== Cleaning complete ====" />
    </target>

    <target name="init" depends="clean">
        <echo message="==== Initializing... ====" />
        ...
        <mkdir dir="${deployed_libraries}" />    <!-- Create deployed_libraries subdirectory -->
        ...
        <echo message="==== Initialization complete ====" />
    </target>

    <target name="deploy" depends="compile,updatedb">
        <echo message="==== Creating WAR file and deploying... ====" />
        <!-- Copy all the files needed to build WAR: -->
        <copy todir="${build}/classes/resources">
            <fileset dir="${src}/resources">
            <include name="**/*" />
        </fileset>
        </copy>
        <copy file="${src}/log4j.properties" todir="${build}/classes"/>
        <copy file="webplatform-servicelocator.log" todir="${build}/classes"/>

        <copy todir="${deployed_libraries}" flatten="true">
            <fileset dir="${lib}">
                <include name="**/*.jar" />
                <exclude name="**/*ant-contrib*" />    <!-- only used by build.xml -->
                <exclude name="**/*sqlscript*" />      <!-- only used by build.xml -->
                <exclude name="**/*mockito*" />        <!-- only used in testing -->
            </fileset>
        </copy>

        <war destfile="${dist}/blackpearl.war" webxml="${web}/WEB-INF/web.xml">
            <fileset dir="${web}" />
            <lib dir="${deployed_libraries}" />
            <classes dir="${build}/classes/" />
            <manifest>
                 <attribute name="Built-By"               value="Snapfish Web Platform Team" />
                 <attribute name="Implementation-Title"   value="User Account Service" />
                 <attribute name="Implementation-Version" value="0.9" />
                 <attribute name="Implementation-Vendor"  value="Hewlett-Packard Development Company, LP." />
             </manifest>
        </war>

        <copy file="${dist}/blackpearl.war" todir="${deploy}" />
        <echo message="==== Deploy complete ====" />
    </target>

Once the lib subdirectory is flattened and unused JARs are excluded, this is the result.

  $ jar -xf project.war
  $ tree WEB-INF/lib
  WEB-INF/lib
  |-- antlr-2.7.6.jar
  |-- asm-3.1.jar
  |-- c3p0-0.9.1.jar
  |-- commons-collections-3.1.jar
  |-- commons-configuration-1.7.jar
  |-- commons-lang-2.6.jar
  |-- gson-1.7.1.jar
  |-- hibernate3.jar
  |-- hibernate-jpa-2.0-api-1.0.1.Final.jar
  |-- javassist-3.12.0.GA.jar
  |-- jersey-client-1.6.jar
  |-- jersey-core-1.6.jar
  |-- jersey-json-1.6.jar
  |-- jersey-server-1.6.jar
  |-- jta-1.1.jar
  |-- log4j-1.2.16.jar
  |-- mongo-2.6.3.jar
  |-- morphia-0.99.jar
  |-- mysql-connector-java-5.1.16-bin.jar
  |-- oauth-server-1.10.jar
  |-- oauth-signature-1.10.jar
  |-- org.springframework.transaction-3.0.6.RELEASE.jar
  |-- postgresql-9.1-901.jdbc4.jar
  |-- servlet-api.jar
  |-- slf4j-api-1.6.1.jar
  `-- webplatform-servicelocator-1.0.jar