Wednesday, November 14, 2007

Building a Tapestry application with ANT

Blasphemy! :). If you have a gander at the still-in-progress Tapestry tutorial you'll see the section on setting up your environment makes mention of using Maven to do the development of your brand-spanking-new Tapestry 5 based application. As nice and automatic the Maven way is (and I duly understand that Maven is like ANT on steroids) I have grown attached to my nice simple XML build files with ANT. I also have a bunch of other software I already know how to easily (and not so easily) integrate with the ANT build system. Thus, my first task, as I have chosen to accept it, is to construct a build.xml file that will let me build the Tapestry part of Buccoo with ANT.

Of course you can't make something "better" without seeing how the existing thing ticks - at least basically. So I followed the instructions, downloaded Maven and typed that handful of a command line. A short time later (ok, a while later - the 'net connection was slow) I had a neat sample application up and running. Jetty is pretty cool, but I want my stuff running on Tomcat, darnit.

So what did the quickstart show me? Without digging around, I saw I needed two java files, a properties file for the logging, a tml template file and a web.xml file. I had no clue where it was finding the jar files (I guess it's in a Maven xml config file somewhere). Short on patience I decided to download the binary build of Tapestry 5.0.6 which I figured MUST have the required jars in it. In the zip file there was a tapestry-tutorial1 folder. In the war archive (which is really a zip file with some meta data in another file) of the tapestry-tutorial1 folder I saw everything I needed in the WEB-INF/lib folder (well, except the tapestry-tutorial1.jar).

I copied the jar files to the C:\work\buccoo\lib folder. Back on track it was time to build my build.xml so I can quasi follow the tutorial but with ANT.

The build.xml resides in the C:\work\buccoo folder and starts off pretty simply.

<project name="Buccoo" default="build" basedir=".">
</project>

I named the project, set the default target to build (compile) and the base directory to the current directory. I need to define the build target though, otherwise ANT would complain.

<project name="Buccoo" default="build" basedir=".">
<target name="build" description="Builds the Web Application">
</target>

</project>

I need to make sure that certain folders exist (because the ANT build fails if the folders aren't there to copy stuff to). I will call this new task prepare! I think build should be dependent on it. All it should really do is create the final structure so all ANT has to do is zip it up and voila! A war file.

<project name="Buccoo" default="build" basedir=".">
<target name="prepare" description="Creates the Web Application's war directory">
</target>


<target name="build" description="Builds the Web Application" depends="prepare">
</target>
</project>

Time to fill in some gaps, let's fill in prepare. Time to create the war directory, the WEB-INF sub directory and its two sub-directories lib and conf. The lib directory is standard, conf is something I use for just-in-case configs that need to be deployed with the war. I'll also create another work folder (this time under res) which will have classes and lib. The classes folder should have the compiled classes of all the sources and the lib should have a jar of those classes. That makes it easy to copy to the WEB-INF/lib of the final war. I also added an ANT property tag so I can easily reference the application's name (used in naming jar and war files) easily.

<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />

</target>

<target name="build" description="Builds the Web Application" depends="prepare">
</target>
</project>

Why wouldn't we just want to use WEB-INF/classes like everyone else? Good question. Besides being neater (ok, personal opinion I admit), there seems to have been a bug with classes that use Tapestry which are not in jars not being able to find themselves and the few extra seconds to avoid that bug seems worth it to me.

Let's get the build target doing something, like compiling the source files. The javac compiler expects all those jars that are in the lib folder to be in the classpath, so we create a classpath path reference and use that when compiling.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>


<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>


</target>
</project>


The path tag allows us to reference all the files referenced to by the fileset, which happens to include all the jars in the lib folder. The javac tag compiles all the java files in the src folder and references the classpath to use when compiling via the classpath tag. For good measure I also added a file copy function that copies all files *.properties into the classes folder. This is for stuff that still relies on a properties file, like log4j.

Now that everything's compiled, time to put what we can in the war folder so we can just zip it up to package it. The first thing we do is package all the compiled classes into a jar file by itself. This is done by the jar task in the build target (that's the one that names it app.name-core.jar which resolves to Buccoo-core.jar). We also add another target - package. All this does is jar every single file we have in the war folder and name it Buccoo.war.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>


</project>


We're almost done. Web application archives are special in that they need a web.xml to tell the Servlet container (like Tomcat) configuration things about routing requests to the application. Without that file, it won't work. Luckily, we have a web folder that contains all the non-java source resources we need to create this war file.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


We copied the web.xml file across as well as some XML files (who knows, may need them for something), tml files (Tapestry template files) as well as swf files (the output of the OpenLaszlo UI is planned to be in flash). We could always add additional file extensions for images, javascript, css or whatever else we want to copy.

Funny thing, the war still wouldn't work! Arg, we forgot to copy the jars in the lib folders across to the WEB-INF/lib. Ok, now we know what to do, let's do it.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


We first copy all the jars in the lib folder across to WEB-INF/lib and then copy all the jars from the res/work/lib folder. That should be only one file Buccoo-core.jar. Sometimes things get so messed up with my working folders I just want to clean up. Although not necessary, below I've added a clean target which removes all the temporary folders (good thing they're created in the prepare target).



<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="clean" description="Deletes the Web Application's war directory and web archive file">
<echo message="Deleting ${app.name}'s war directory and web archive file ..."/>
<delete dir="${basedir}/war"/>
<delete file="${basedir}/${app.name}.war"/>
<delete dir="${basedir}/res/work"/>
</target>


<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>

</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


The very last thing to do is deploy the war file (provided we wrote some java source code that compiles in there). While this can be done manually, for development purposes I want to create an ANT target. This target would be named deploy. It would be responsible for stopping the Tomcat servlet container (in this case the one used by OpenLaszlo), removing any previous incarnation of our project (the war file and the exploded folder), starting back Tomcat and copying our war file so it can be re-exploded. Let's also add a property for the tomcat home folder so we can make some re-configuration easier if necessary.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />
<property name="tomcat.home" value="C:/work/OpenLaszloServer4.0.6/Server/tomcat-5.0.24" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="clean" description="Deletes the Web Application's war directory and web archive file">
<echo message="Deleting ${app.name}'s war directory and web archive file ..."/>
<delete dir="${basedir}/war"/>
<delete file="${basedir}/${app.name}.war"/>
<delete dir="${basedir}/res/work"/>
</target>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>

</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

<target name="deploy" description="Deploys the package into a container" depends="clean,package">
<exec dir="." executable="cmd">
<arg line='/c C:\work\OpenLaszloServer4.0.6\Server\lps-4.0.6\lps\utils\stopTomcat.bat'/>
</exec>

<sleep seconds="2"/>

<delete file="${tomcat.home}/webapps/${app.name}.war"/>

<delete dir="${tomcat.home}/webapps/${app.name}" />

<exec dir="." executable="cmd" spawn="true">
<arg line='/c C:\work\OpenLaszloServer4.0.6\Server\lps-4.0.6\lps\utils\startTomcat.bat'/>
</exec>

<sleep seconds="2"/>

<copy todir="${tomcat.home}/webapps/">
<fileset dir="${basedir}">
<include name="${app.name}.war"/>
</fileset>
</copy>
</target>

</project>


OpenLaszlo uses two batch files on Windows to do its starting and stopping. The stopTomcat.bat file stops the OpenLaszlo service. The way that the startTomcat.bat starts the service though, when we call that it needs to be spawned so it doesn't keep the last task from executing (which is copying the war file - rather important).

There you have it a build.xml file that lets us build Tapestry 5 based applications. In my next post I'll see if I can get some rudimentary XML output from it.

No comments: