Once again, an attempt to get two stable, mature, well-documented open source Java tools to talk to each other has resulted in several hours of frustration, as well as all the bad karma associated with using Those Words (as my Grade 3 teacher called them) out loud. I finally figured out how to avoid the problem, but I still don't understand why it happens in the first place. If any Ant and/or Hibernate gurus would care to enlighten me, I would be grateful.
Here's what happened:
I wanted to create a near-trivial example application to introduce future generations of students to Hibernate (an object/relational mapping framework for Java). Hibernate gives developers two options: write a JavaBean class, then write a mapping file to describe how that class's members map to database tables, or write the mapping file, and then have Hibernate generate the JavaBean class. Half of this fall's students did it one way, half did it the other; based on their experiences, I decided to go the code generation route.
I created four files:
hibernate.cfg.xml: the Hibernate configuration file.
build.xml: the Ant build file. (If you haven't seen Ant, it's a pure-Java replacement for Make, which has become the standard build tool for Java programming).
src/User.hbm.xml: the Hibernate mapping file for my
src/Test.java: a simple test program. I initially used JUnit, but when things started to go pear-shaped , I yanked it out in an attempt to isolate the problem.
build.xmlcreates two output directories at the start of the build process: one called
gen, for generated code, and another called
class, for compiled
build.xmlhas four targets:
clean: deletes the
classdirectories, and also the
datadirectory (described below).
codegen: invokes Hibernate's
schema: creates a
datadirectory, and invokes Hibernate's
SchemaExportTaskto generate database schema files in that directory.
src/User.hbm.xml, checks that it's consistent with
class/User.class(compile-time checking---excellent), and creates
data/hippo.properties, which between them tell HSQLDB (the database I'm using) what table(s) to create, and how.
The examples in Hibernate in Action (the book I'm using) show several examples of
hibernate.properties files, which use the older Java properties file syntax, but when it comes to XML configuration files, the book says, "Go see your install documentation." I didn't find that documentation helpful; in particular, it wasn't clear when the XML configuration file's properties had to have the same names as were used in the plain text configuration file, and when they had to be different. Hibernate's error messages didn't help at all: all I could do to debug was make more-or-less random changes to the configuration file, and see what effect they had.
I finally annealed  my configuration file into a working state. A fresh cup of tea, and I was ready to start making some progress---except now Ant was unhappy. The four targets in
build.xml are linearly dependent:
schema depends on
compile depends on
ant schema should therefore erase the working directories, generate
gen/User.java, compile it and
src/Test.java, and generate a database schema.
When I tried it, the first three steps worked perfectly, but schema generation failed with an error message saying, "Unable to read User.class". My first thought was that this was yet another classpath problem, but then I discovered that if I ran
ant schema again, without running
ant clean in between, Ant and Hibernate would generate my schema for me.
After ninety minutes and a lot of bad language, I convinced myself that this was some sort of timing problem. If I ran
ant compile, then ran the schema-generation target on its own, everything worked. If I left the dependency between the
compile targets in
build.xml, and tried to do everything in a single Ant run, it failed. I went so far as to revive a little Python inventory tool I wrote a while back, which walked through the directory tree, recording timestamps, file sizes, and MD5 checksums. As far as I could tell, there was no difference in the files generated by running Ant in two steps, versus running it in one.
And no, it isn't Windows-specific: I get exactly the same behavior on Debian Linux...
At this point, I know how to work around the problem (run Ant twice), and could go back to working on my Hibernate example. But how am I going to explain this to students? "Hi, everyone, this term we're going to be using Ant. It's a replacement for Make, and sometimes, well, sometimes you just have to run it a couple of times to get it to work." I don't think it's acceptable for software to act like a spoiled six-year-old ("I won't! I won't! I won't I won't I won't and you can't make me!"); I certainly don't feel comfortable telling my students that when it does, they should just put up with it.
 "Pear-shaped" is a British expression meaning "gone bad". No idea how it originated; if anyone knows, I'd like to hear.
 "Annealing" is the process of banging on metal to get the kinks out. "Simulated annealing" is an optimization technique in which you make random changes to your proposed solution, keeping those that make things better, and throwing aways those that don't. Simulated annealing is a lousy way to debug programs.comments powered by Disqus