Coming Up for Air

Maven and Annotations: Not as Easy as It Should Be

Over the past year or so, I’ve been slowly migrating — somewhat accidentally — to Maven. I had even begun migrating the build environment for Scales from Ant to Maven, but hit a huge roadblock: annotation processing. Scales depends heavily on compile-time annotation processing, and the only thing I could find on the web was other people with the same problem. As I was working on some of my JSFOne examples, I really wanted to use Maven, as the NetBeans support is a lot cleaner with Maven versus an externally maintained Ant build file, so I set to with renewed purpose. Finally, I seem to have found the right query string, as I appear to have solved my problem. The solution? Ant.

One of the vaunted features of Maven is the ability to embed Ant scripts in your POM file. My first thought when I ran into the problem above was exploiting that capability, but those attempts were thwarted by one of Maven’s biggest weaknesses: poor documentation. As I noted, though, I finally found a web page that had something close to what I needed that I was able to work out the rest. Since my JSFOne examples have the same compilation requirements as does Scales, I was able to pull the annotation processing tasks from the Scales build, giving me this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <id>process_component_annotations</id>
      <phase>generate-sources</phase>
      <configuration>
        <tasks>
          <property name="target.dir" value="target/classes/META-INF"/>
          <mkdir dir="${target.dir}" />
          <apt srcdir="src/main/java"
              preprocessdir="generate"
              destdir="${target.dir}"
              includes="**/*.java"
              compile="false"
              debug="true"
              factory="com.sun.faces.mirror.FacesAnnotationProcessorFactory"
              source="1.5"
              target="1.5">
            <option name="generate.runtime" value="" />
            <option name="namespace.uri"
              value="http://steeplesoft.com/jsfone/jsf2comps" />
            <option name="namespace.prefix" value="jsfone" />
            <option name="taglibdoc"
              value="src/main/conf/tag-descriptions.xml"/>
            <option name="localize" value="" />
            <classpath>
              <path refid="maven.compile.classpath" />
            </classpath>
          </apt>
          <move file="${target.dir}/taglib.xml"
            tofile="${target.dir}/jsf2comps.tld"/>
          <move file="${target.dir}/facelets.taglib.xml"
            tofile="${target.dir}/jsf2comps.taglib.xml"/>
        </tasks>
        <sourceRoot>generate</sourceRoot>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>org.apache.ant</groupId>
      <artifactId>ant</artifactId>
      <version>1.7.0</version>
    </dependency>
  </dependencies>
</plugin>

It’s not really pretty, but we are talking about a Maven POM. ;) As I’m sure you’ve surmised by now, I’m not a Maven expert, but here is my understanding of things. We’re telling Maven to run these tasks when the generate-sources phase is run (if you can find documentation on what the Maven lifecycle is, I’d love to see it). The tasks run are, I think, pretty self-explanatory (their purpose is outside the scope of this post either way ; ). Note, though, that we can run as many arbitrary Ant tasks as we want.

One feature that I like a lot is the sourceRoot entry. With that line, we’re telling Maven to add the generate directory to the build path. Since the annotation processor creates the source for JSP Tag files, we need to compile that class, and this takes care of that for us.

One remaining problem was that this creates some directories and files that Maven doesn’t delete when the clean goal is run. To fix that, we add this XML snippet to the plugin description above:

1
2
3
4
5
6
7
8
9
10
11
12
<execution>
  <id>clean_generated</id>
  <phase>clean</phase>
  <goals>
    <goal>run</goal>
  </goals>
  <configuration>
    <tasks>
      <delete dir="generate" />
    </tasks>
  </configuration>
</execution>

With that, we can now issue a mvn clean and get rid of the generate directory that apt creates. Snazzy.

Maven experts will look at this and doubtless see many ways to improve the code, and some will likely suggest the Maven apt plugin from Tobago. What this represents, though, is a working solution in spite of Maven, ugly as it may be. Of course, I’m certainly open to suggestions and advice, but what I have is working, so I’m not going to lose much sleep over it. Hopefully, this will help someone else. Better yet, maybe the Maven developers will release a meaningful update to Maven that fixes problems like this. :)

tags: Ant Java Maven

Quotes

Sample quote

Quote source