Monday, March 05, 2007

From maven to mvn : Part 5 -- A Groovy Timestamp

Good afternoon and happy Monday! I hope you had a great weekend, I know I did. I have to apologize for today's entry up front. It isn't about writing a plugin as I had hoped it would be. It's also somewhat brief. Well, it's Monday and there have been more than the usual number of distractions...

I've been bothered about abusing the maven-buildnumber-plugin to set a timestamp property used in ${foo} replacement. I happened to be doing some reading over the weekend and came across the groovy-maven-plugin. Which lets you run a random bit of groovy script during your build process. Now I still intend to explore the exciting world of creating my own maven plugins but I think this is a good place to start. In fact, when I *do* write my own plugins in the future they will very likely be written in groovy using this technique.

So, today's brief entry will (a) drop the buildnumber plugin and (b) use the groovy-maven plugin to replace its functionality.

Begin by adding repository entries to your pom:
<repositories>
<repository>
<id>Codehaus Snapshots</id>
<url>http://snapshots.repository.codehaus.org/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>Codehaus Snapshots</id>
<url>http://snapshots.repository.codehaus.org/</url>
</pluginRepository>
</pluginRepositories>

Note: If you already have repositories declared just add these new entries, don't blow away what you've got.

Next, declare the plugin and some script for it to execute:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
<body>
import java.util.Date
import java.text.MessageFormat
def timestamp = MessageFormat.format("{0,date,yyyy-MM-dd HH:mm:ss}", new Date())
System.setProperty("timestamp", timestamp)
</body>
</source>
</configuration>
</execution>
</executions>
</plugin>


That's it! Now, during the generate-resources phase of the maven build lifecycle groovy will execute the script to set the system property timestamp to the current time.

That's it for today. I have to run off to take care of some other things. I promise you a better Part 6.

Thursday, March 01, 2007

From maven to mvn : Part 4 -- Transitive Dependencies

So I took a break from maven yesterday to do some performance investigation using JAMon and AspectJ. We don't need no fancy-pants monitoring tools. Just a bit of advice, a Monitor or two and an editor that can handle huge log files.

But that's not what I'm here to talk about today.

Today I'm continuing the m1-to-m2 migration and turning my attention to my nasty pile of dependencies. With m2's way cool transitive dependency management I hope to drop my 300 lines of dependencies in pom.xml to something a bit more manageable.

Now you probably don't want me to dump the original 300 lines here so I can't really show you what I'm starting with. However, the high level framework bits include: spring, hibernate, acegi, aspectj and misc Jakarta commons. If you've worked with any of these you know that each one introduces still other dependencies you have to deal with.

To get started I simply commented out all of the dependencies we didn't produce in-house and started compiling. AspectJ was the first thing mvn complained about so now I have to go figure out how to add it correctly... I mentioned this link earlier (Part 2 I believe). I'm not a big fan of repeating someone else's words so please take a moment to read the link. I'll wait.

Did you see this bit: "site:www.ibiblio.org maven2 log4j" If you didn't, go back and look for it. That is the secret to finding your m2 dependencies. I will caution you, however, that maven-metadata.xml may not always list the most recent available versions. It's worth doing a directory listing to see what is there.

For instance, "site:www.ibiblio.org maven2 aspectj" gets me to a maven-metadata.xml that does not list version 1.5.2a which, as it turns out, is the one I want. The directory listing, however, shows me a 1.5.2a subdirectory which has exactly what I need.

Also in maven-metadata.xml you will find the appropriate groupId and artifactId values you need for your dependency. Armed with all of this information I can now add the appropriate pom.xml entry:
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.2a</version>
</dependency>

Good. That was easy. Now rinse & repeat for my other dependencies and lets see where we end up...

Well, that didn't last very long. My very next dependency is hibernate. My google search showed up beanlib (which is really cool BTW) and the old net.sf.hibernate stuff but not the 3.2.1.ga version I need. Fortunately, I know how the m1 repository is layed out and a bit about how the m2 repository is layed out. On a hunch I try a reasonable URL and find something useful. (I also noticed that 3.2.2.ga is available so I may consider an upgrade soon. But I digress...) Now I know the groupId and artifactId values for my hibernate dependency so that gets added to my pom.xml

Now rinse & repeat. Occasionally a dependency shows up with a groupId or artifactId that I wasn't quite expecting from my m1 days but those things are easy to work with.

javax.transaction introduced a new wrinkle. Apparently you have to go get that from Sun yourself. Thanks guys. <sigh/> Fortunately m2 tells us exactly what to do:
Missing:
----------
1) javax.transaction:jta:jar:1.0.1B

Try downloading the file manually from:
http://java.sun.com/products/jta

Then, install it using the command:
mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta \
-Dversion=1.0.1B -Dpackaging=jar -Dfile=/path/to/file

Path to dependency:
1) com.myCompany.personal.jcej:contacts:war:1.0-SNAPSHOT
2) org.hibernate:hibernate:jar:3.2.1.ga
3) javax.transaction:jta:jar:1.0.1B
Oh what fun... However, if you run across other dependencies that are not available at the venerable ibiblio you can use this same technique to install them to your local repository. Certainly not ideal but tolerable until they do become available.

It is worth pointing out that as you make your way through your dependencies you may be able to remove some you added previously. For instance, I added org.hibernate/hibernate because my build broke without it. Later I added org.hibernate/hibernate-annotations because the build broke on that. The later requires the former so I can remove my explicit org.hibernate/hibernate dependency and rely on the transitive dependency from org.hibernate/hibernate-annotations. It is a bit more work to backtrack this way but in the long run it will make for a much smaller and easier to maintain pom.xml. For simplicity it is worth my time to inspect my dependencies' pom.

While exploring that, because I know acegisecurity depends on spring, I discovered that some plugins specify their dependency versions via properties. Now that's a great idea! Because it centralizes dependency version management and lets me override their required version by overriding the property.

Thus I now have a properties section in my pom.xml something like this:
<properties>
<spring.version>2.0.1</spring.version>
...
I just have to pay attention to my dependencies dependencies. But I was going to do that anyway. Using the properties is easy:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>${spring.version}</version>
</dependency>
[ time passes ]

So after about an hour (which isn't bad considering the normal afternoon interruptions) I get to the point where things are successfully compiling. But my unit tests are failing!? Looking at the surefire reports clues me in to the thought that maybe some of my dependencies aren't consistent:
    java.lang.NoSuchMethodError: org.springframework.util.ObjectUtils.nullSafeToString(Ljava/lang/Object;)Ljava/lang/String;
That don't look good...

Build again with the tests disabled (mvn -Dmaven.test.skip=true install) and see what we have in our war's lib directory. Yea... Well... Who put spring 1.2.8 jars in there? This is a case where transitive dependencies hurt rather than help.

OK, roll the sleeves up and crank up the tunes. Lets go figure this one out. First, we need to tell maven to tell us more: mvn -X -Dmaven.test.skip=true install. It's probably wise to capture that via nohup or script since it will be a tad verbose. I know that acegisecurity depends on spring and after a few minutes I realize that the spring.version property I was so excited about didn't really help me. Sure, I set it to 2.0.1 in my own pom.xml but that didn't override the value in the acegisecurity dependency's pom. Drat. After a bit of reading I come to the conclusion that I need to exclude spring from acegisecurity's dependencies. Once I do this I can specify the spring versions I want in my own pom.xml and we should be in good shape.

Better but still not good. We have the correct version of spring and that's good. But my hsqldb-based unit tests are failing. They're complaining about "The database is already in use by another process" and I wasn't getting that before I started mucking about with transitive dependencies. This turned out to be similar to the spring problem in that struts-menu was pulling an old version of hsqldb.

There seems to be some difference in the way transitive dependencies are excluded. In the acegisecurity-uses-spring case spring is specified in the dependencyManagement section. When I add an exclusion to my acegisecurity dependency declaration all works as expected. Namely, my spring dependency is used instead of acegi's. However, struts-menu declares its dependency on hsqldb in its dependencies, not under dependencyManagement. When I add an exclusion to struts-menu not only does mvn not include the version of hsqldb that struts-menu wants but it does not include *any* version of hsqldb though it is clearly listed in my own dependencies. Could be operator error... Just be cautious. (If you have any word on this please drop me a comment so I can quit pulling my hair out over it.)

That's it for today. My m1-built war still has different dependencies than my m2-built war but I'm confident that those remaining dependencies can be solved by applying the steps above. It's raining and 5:00 and I would like to get home before dinner gets cold. I'll wrap this up in the morning and if I make any amazing discoveries I'll document 'em here for you. Otherwise you can safely assume that things went well.

Part 5 coming soon. I'm not quite sure what that'll be but it might have something to do with creating my own plugin. Check back soon...

(Oh, and if you're keeping score, my 300 lines of dependencies are now about 120 lines. Maybe less if I drop the odd comment here and there. I call that an improvement!)