ActiveRecord Naming Conventions with Spring JDBC

java ruby on railsOne of the best choices we made at my current job was splitting the web development from the heavy message processing system, allowing the former to be written in Ruby on Rails and the latter to be written in Java. However, for ease of development, we allowed the two code bases share a single database. What then to do about database naming conventions? Those familiar with ActiveRecord will know that it is very particular about its database naming conventions so it was up to the Java code to play nicely. The first solution to present itself required hardcoding database names in each Java object but a more elegant solution seemed possible and, after the discovery of one small but important 3rd party project, it revealed itself.

Inflection

In Rails, the class responsible for converting object names to database names is called Inflections. For those unfamiliar with the term (as I was), inflection is the modification of a word to express different grammatical categories. Armed with this new knowledge I set about googling and was relieved to find that Tom White had already implemented a Java Inflector. However, the Java Inflector only implements the pluralization aspect of the Rails Inflections; for this to work I was going to need some additional string manipulation tools. Luckily the old Apache Commons StringUtils was there to lend a helping hand. Finally, confident in my string manipulation tools, the next step was to look into the JDBC side of things.

ORMs

When dealing with mapping objects to and from a database Hibernate is always the name that pops up. I’ve worked with it in the past and, while incredibly powerful, I always felt like I was wielding a hatchet when what I wanted was a surgical knife. I’ve heard its configuration has been simplified with JPA but its still a beast of a project that relies on a bit more magic than I’m comfortable with. In a more recent project I played with iBatis (now myBatis) and enjoyed the simplicity but felt overwhelmed by the xml configuration. Version 3.0 supports annotation configuration which greatly simplifies this but I was looking for something based on naming conventions so I wouldn’t have to supply the mapping myself. I don’t mind getting down and dirty with some SQL so I opted for Spring JDBC. Spring provides a super thin layer on top of raw JDBC which takes what is a fairly developer hostile API and makes it a breeze to use, all while adding some wonderful utilities to speed development.

Putting It All Together

AbstractDbObj

The first step was to create an AbstractDbObj to allow all other database objects to inherit from. This is where the logic for creating db names using the Inflector and StringUtils lives along with some other shared code.

import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;
import static org.apache.commons.lang.StringUtils.join;
import static org.apache.commons.lang.StringUtils.lowerCase;
import static org.apache.commons.lang.StringUtils.splitByCharacterTypeCamelCase;
import static org.jvnet.inflector.Noun.pluralOf;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;

public abstract class AbstractDbObj  implements DbObj {
  private static final String[] EXCLUDED_FIELD_NAMES = new String[] { "columnNames", "map", "fieldNames", "tableName" };
  private static final Predicate<String> IS_COLUMN = new Predicate<String>() {
    private final ImmutableSet<String> notColumns = ImmutableSet.of( "class", "tableName", "columnNames", "fieldNames" );
    public boolean apply( final String propName ) {
      return !notColumns.contains( propName );
    }
  };

  private static final ConcurrentMap<Class<? extends LocaDbObj>, String> TABLE_NAME_LOOKUP = new ConcurrentHashMap<Class<? extends LocaDbObj>, String>();

  private static final Function<String, String> TO_COLUMN = new Function<String, String>() {
    public String apply( final String fieldName ) {
      return underscore( fieldName );
    }
  };

  private static String tableName( final Class<? extends LocaDbObj> clazz ) {
    return pluralOf( underscore( clazz.getSimpleName() ) );
  }

  protected static String underscore( final String camelCasedWord ) {
    return lowerCase( join( splitByCharacterTypeCamelCase( camelCasedWord ), '_' ) );
  }

  private Integer id;
  private final Collection<String> fieldNames;
  private final String tableName;
  private final Map<String, Object> map;
  private final Collection<String> columnNames;

  @SuppressWarnings( "unchecked" )
  protected AbstractLocaDbObj() {
    super();
    final Class<? extends LocaDbObj> clazz = getClass();
    String tblName = TABLE_NAME_LOOKUP.get( clazz );
    if ( tblName == null ) {
      tblName = tableName( clazz );
      TABLE_NAME_LOOKUP.put( clazz, tblName );
    }
    tableName = tblName;
    final BeanMap beanMap = new BeanMap( this );
    fieldNames = filter( beanMap.keySet(), IS_COLUMN );
    columnNames = transform( fieldNames, TO_COLUMN );
    map = beanMap;
}

  @SuppressWarnings( "unchecked" )
  protected AbstractLocaDbObj( final String tableName ) {
    super();
    this.tableName = tableName;
    final BeanMap beanMap = new BeanMap( this );
    fieldNames = filter( beanMap.keySet(), IS_COLUMN );
    columnNames = transform( fieldNames, TO_COLUMN );
    map = beanMap;
  }

  @Override
  public boolean equals( final Object obj ) {
    return EqualsBuilder.reflectionEquals( this, obj, EXCLUDED_FIELD_NAMES );
  }

  public String getColumnName( final String fieldName ) {
    return underscore( fieldName );
  }

  public Collection<String> getColumnNames() {
    return columnNames;
  }

  public Collection<String> getFieldNames() {
    return fieldNames;
  }

  public Integer getId() {
    return id;
  }

  public String getTableName() {
    return tableName;
  }

  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode( this, EXCLUDED_FIELD_NAMES );
  }

  public void setId( final Integer id ) {
    this.id = id;
  }

  public Map<String, Object> toMap() {
    return map;
  }

  @Override
  public String toString() {
    return new ReflectionToStringBuilder( this ).setExcludeFieldNames(EXCLUDED_FIELD_NAMES ).toString();
  }
}

A few things to point out. First, I’m making use of the Google Collections Function and Predicate. For more info on that check out the developer.com article. Second, I’m using Apache BeanUtils BeanMap which uses reflection to easily create a property to value map of a JavaBean. Finally, I’m storing the table names in a lookup since the pluralization a very expensive operation. The rest should hopefully be somewhat straight forward.

SimpleJdbcInsert

Using Spring’s SimpleJdbcInsert, combined with the heavy lifting in the abstract class, insertions are practically free as you can see in the following code.

// Get Datasource from Spring Context
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert( dataSource ).withTableName( dbObj.getTableName() ).usingGeneratedKeyColumns( "id" );
final int id = jdbcInsert.executeAndReturnKey( new BeanPropertySqlParameterSource( dbObj ) ).intValue();
dbObj.setId( id );

Note: you can reuse a SimpleJdbcInsert since it is thread safe, I created a new one in the above code just to show how it is done.

SimpleJdbcTemplate

After inserts, the other two pieces of required functionality are query and update, both of which are supplied by Spring’s SimpleJdbcTemplate. The trick here is to make generous use of Spring’s BeanPropertySqlParameterSource and BeanPropertyRowManager which effortlessly map between JavaBean properties and database columns.

Lets start with the simple case of deleting an object.


simpleJdbcTemplate.update( "DELETE FROM " + dbObj.getTableName() + " WHERE id = :id", new BeanPropertySqlParameterSource( dbObj ) );

You’ll notice the string “id = :id” in the query. This allows Spring to do value substitution, using the BeanPropertySqlParameterSource to replace :id with the actual value of .getId() from the dbObj. With this in mind we can step up to the more complicated update case.

final Collection<String> parts = buildUpdateParts( dbObj );
if ( parts.isEmpty() ) { return; }
final String query = "UPDATE " + dbObj.getTableName() + " SET " + join( parts, ", " ) + " WHERE id = :id";
simpleJdbcTemplate.update( query, new BeanPropertySqlParameterSource( dbObj ) );

private static final Collection<String> buildUpdateParts( final LocaDbObj dbObj ) {
  return transform( filter( dbObj.getFieldNames(), not( IS_ID ) ), new BuildPart( dbObj ) );
}

private static final class BuildPart implements Function<String, String> {
  private final LocaDbObj dbObj;
  public BuildPart( final LocaDbObj dbObj ) {
    this.dbObj = dbObj;
  }
  public String apply( final String propName ) {
    return dbObj.getColumnName( propName ) + " = :" + propName;
  }
}

This code again makes use of google collections but the point is that it takes the field names and the column names from the DbObj and creates snips of sql setting the column name to the field name identifier (i.e. the field name prefixed with ‘:’) just like was done in the deletion case with “id = :id”. Continuing to build on the previous examples we finally come to the query case. This one is a bit difference since we are interested in reading results from the database as well as building a query so we will have to deal with the BeanPropertyRowMapper.

final Collection<String> parts = buildQueryParts( dbObj );
if ( parts.isEmpty() ) { return ImmutableList.of(); }
final String query = "SELECT " + columnNames + " FROM " + dbObj.getTableName() + " WHERE " + join( parts, " AND " );
return result = simpleJdbcTemplate.query( query, new BeanPropertyRowMapper<DbObj>( dbObj.getClass() ), new BeanPropertySqlParameterSource( dbObj ) );

private static final Collection<String> buildQueryParts( final LocaDbObj dbObj ) {
  return transform( filter( dbObj.getFieldNames(), new HasValue( dbObj.toMap() ) ), new BuildPart( dbObj ) );
}

private static final class HasValue implements Predicate<String> {
  private final Map<String, Object> map;
  public HasValue( final Map<String, Object> map ) {
    this.map = map;
  }
  public boolean apply( final String propName ) {
    return map.get( propName ) != null;
  }
}

Hopefully by this point the google collections syntax is becoming familiar. We want to include only object fields that are non null so we use the HasValue Predicate to check each property by name. From there on the rest is quite similar to the update method where we build parts setting each column name equal to its field name identifier. The only other difference is we use the BeanPropertyRowMapper so Spring can return us a list of the correct type of DbObj.

While certainly a bit heavy on the code I hope this will be useful to those trying to create a simple, straight forward database layer with Spring and ActiveRecord naming conventions. As a bit of a tease, the full code base that this is pulled from contains an genericized DAO object that contains basic annotation driven caching as well as the ability to execute CRUD commands via example objects, ids, sql snippets (i.e. where clauses), or full raw SQL all while keeping things nice and type safe as well as sql injection free using PreparedStatements and value substitution.

Approximating Java Case Objects without Project Lombok

LombokOver the past few months Dick Wall of the Java Posse has been talking up Project Lombok and for good reason. As he points out, its great for reducing boilerplate code in basic Java data objects. However, the more important point that Dick makes is that it prevents stupid errors, particularly when adding new object fields later on in the development cycle. The only unfortunate issue is that Project Lombok requires a compiler hack that can be rather confusing to those not using a supported IDE or for those coding outside of an IDE. However, Apache Commons provides an elegant solution to this issue.

Project Lombok

Lets start with a quick overview of Project Lombok. At its most basic level it provides a set of annotations (i.e. @Data) that you add to your Java data objects. You simply create private fields in an annotated object and the annotation will cause a full set of constructors, getters/setters, equals/hashCode and toString to be generated in the class file at compile time while keeping a simple and minimal source file. Its an elegant solution but it comes with a fair amount of magic surrounding it.

Alternatives

The simple alternative to Project Lombok is to have the IDE generate the code for you. Eclipse, for instance, is very good at doing this with auto generation of constructors, getters/setters, hashCode/equals and toString. This works wonders when creating the object for the first time but, as Dick points out, its too easy to add a new field and forget about updating toString and hashCode/equals leading to confusing and subtle bugs down the road.

The other alternative, and the one I am promoting, strikes a balance between Project Lombok and IDE generation by auto generating the hashCode/equals and toString and leaving the getters/setters and constructors up to the developer as these are necessary for the new field to be of any use.

Apache Commons

Buried deep in the Apache Commons is the builder package containing utilities such as EqualsBuilder, HashCodeBuilder, and ReflectionToStringBuilder. I don’t remember when I first discovered these gems but once I did the lightbulb went off; if used them in an Abstract base class they would ensure proper implementations of equals/hashCode and toString without having to mess with every data object I create.

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public abstract class AbstractBaseObj {

    @Override
    public boolean equals( final Object obj ) {
        return EqualsBuilder.reflectionEquals( this, obj );
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode( this );
    }

    @Override
    public String toString() {
        return new ReflectionToStringBuilder( this).toString();
    }
}

Drawbacks

Of course this option is not with out its own drawbacks compared to Project Lombok. First off, as mentioned before, it does not deal with getters/setters or constructors. However, as I pointed out, I don’t believe this to be a huge imposition since creating a new field without creating getters/setters and/or a constructor using that field is fairly useless. Additionally, I like that this gives ease and flexibility over which constructors are created. This has been quite handy in the most recent project I’ve been working on.

Another draw back is that it requires an Abstract base class from which all objects must inherit. Annotations are certainly a more elegant solution since it doesn’t require a given object hierarchy. However there are often good reasons for a project specific base object for reasons beyond just equals/hashCode and toString. Additionally, the base class is fully owned by the project with just the few methods necessary being implemented. This is significantly better than having to inherit from a class provided by some 3rd party jar.

The important drawback, however,  is that the Apache Commons solution uses reflection instead of actually generating code to implement equals/hashCode and toString. Depending on how frequently these are used and how performant they need to be this might be a real consideration to use either Project Lombok or to write all the code directly. However, I have not found this to be the case in my project as it never seems to show up as a hot spot in any profiling I have done.

REST calls and JSON results in Java

I recently had the need interact with Twittervision’s RESTful api from Java. As usual, I started googling around for a solution but nothing obviously stood out. There are a handful of RESTful java frameworks, but these focus on providing a RESTful api and not consuming one. Finally, after an annoyingly difficult search I found an impressively simple solution based on Jersey.

The point to realize is that Jersey provides both a sever and a client api. To make use of the client api requires three simple lines:

Client client = Client.create();
WebResource webResource = 
client.resource("http://twittervision.com/user/current_status/bdarfler.json");
String response = webResource.get(String.class);

The result is a string of JSON, but now, how to parse it. After searching around I ended up settling on json-simple. A few simple lines of code and we can get the address for any twitter user.
final JSONObject jsonObj = (JSONObject) JSON_PARSER.parse( response );
if ( jsonObj != null &amp;&amp; jsonObj.containsKey( "location" ) )
{
final JSONObject location = (JSONObject) jsonObj.get( "location" );
return location.get( "address" ).toString();
}

So, once again, simple stuff but not as obvious as I was hoping it would be.

Controlling Log4j Logs with Contexts and Filters

Its a problem we’ve all had. Something has gone awry, you jack up the log level to debug and all of the sudden you are inundated with everything under the sun. While one option could be changing some of the logs to trace level there is another option, using Log4j contexts. Log4j contains two different contexts mapped and nested. In this post I’ll focus on nested.

The first thing to do is to isolate the section of your code that you want to create a context for. Once you have accomplished this you make a static call to NDC.push(). The parameter passed in is the string name for the context you would like create. I recommend pulling these strings out into a constants file ease of use and additionally calling .intern() on them so you can use pointer equality checks later when we code up the filters. When you leave the context you call NDC.pop() and life goes back to normal. Finally, remember to call NDC.remove() when you leave the thread.
The code ends up looking like this:

NDC.push(Constants.HEARTBEAT_CONTEXT);
try{
  if (log.isDebugEnabled()) {
    log.debug("[MASTER/SLAVE] Publishing heartbeat");
  }
  doStuff();
}
finally {
  NDC.pop();
  NDC.remove();
}

Now that we have the logs tagged with the correct context we need to create a log4j filter by extending the filter class and overriding the decide(LoggingEvent event) method. The important concept to remember here is that filters can be chained which means your decision results in either a DENY, ACCEPT, or NEUTRAL decision where deny forces the log to be rejected, accept force the log to be written and neutral passes the decision down the chain. Additionally, you access the context by calling getNDC() on the LoggingEvent object passed in to the decide method.
The code then looks like this:
public class HeartbeatFilter extends Filter
{
  @Override
  public int decide(final LoggingEvent event) {
    if (event.getNDC() == Constants.HEARTBEAT_CONTEXT) {
      return DENY;
    }
    return NEUTRAL;
  }
}

Finally we have to add this filter to our log4j configuration. For this we have to make sure we are using the xml configuration file type as it is more expressive and allows us to define filters to use where as the property configuration file type does not. All we have to do is add a filter tag to an appender in the xml and point it at our class.
It looks something like this:

<appender name="console" class="org.apache.log4j.ConsoleAppender">
  <param name="Target" value="System.out"/>
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
  </layout>
  <filter class="my.package.HeartbeatFilter" />
</appender>

Pimpin' your Ant build with Ivy

As of late Maven has been making headway in the Java build world.  However, there are some who are either in the position of working with an existing system build with Ant or who don’t want to get locked into the Maven file structure.  For those folks there is Ivy to the rescue.

Ivy is an “agile dependancy manager” which recently released 2.0.0-rc1.  Thought jam packed with lovely features the first one that I have put to use is the ability to manage 3rd party dependancies, automagically downloading them, dealing with version conflicts and ensuring that any dependancies they in turn have are dealt with.

Ivy allows for two configuration files, one for describing where to look for jars and one to describe what jars to look for.  Beyond that, the rest of the work is done within your ant build.xml file.  Lets start with the ivysettings.xml file which describes where to look for jars.

<?xml version="1.0" encoding="UTF-8"?>
<ivysettings>  
    <settings defaultResolver="chained"/>  
    <resolvers>  
        <chain name="chained" returnFirst="true">
            <url name="jboss">  
                <artifact pattern="http://repository.jboss.com/maven2/javax/jms/[module]/[revision]/[module]-[revision].jar" />
            </url> 
            <ibiblio name="ibiblio" m2compatible="true"/>
            <filesystem name="local-filesystem">
                <artifact pattern="/local/file/system/[organization]-[revision]/lib/[module].jar" />
                <artifact pattern="/local/file/system/[organization]-[revision]/[module].jar" />
            </filesystem>
        </chain>  
    </resolvers>  
</ivysettings>  

There are a few things to point out here.  First is the resolvers tag. There are a few different type of resolvers and I have chosen to use the chain resolver here.  If you are lucky, everything you need with be in ibiblio and all you would need would be the middle ibiblio tag.  However, if you are using any jars that are not mainstream you will have to end up specifying the url here.  The url tag I’m using here is to deal with the unfortunate issue of jboss not having the latest javax.jms jar up on ibiblio.  Here we specify the url pattern to down load the jar directly as long as the url points at a valid maven style repo.  Notice you can insert the module revision and organization as parameters.  These are passed in from the ivy.xml file which we will discuss next. Also note that in this case there is already a javax.jms jar in ibiblio, just not the right version so we put the url tag first.  Additionally, you might have to deal with some jars that are proprietary or not in a valid maven style repo.  In this case you will have to download these using ant (show later) or store them in source control.  However you can still leverage Ivy by having it pick them up from the local filesystem.

Now onto the ivy.xml file.

<?xml version="1.0" encoding="ISO-8859-1"?>
<ivy-module version="2.0">
    <info organisation="locamoda" module="lms" status="integration" />
    <configurations>
        <conf name="build"  description="build dependancies"/>
        <conf name="runtime" description="runtime dependancies"/>
        <conf name="junit" extends="build, runtime" description="junit dependances"/>
        <conf name="mysql" description="mysql dependances"/>
        <conf name="cobertura" description="cobertura dependances"/>
        <conf name="findbugs" description="findbugs dependances"/>
    </configurations>
    <dependencies defaultconfmapping="*->default">
        <dependency org="com.google.collections" name="google-collections" rev="0.8" conf="build, runtime" />
        <dependency org="log4j" name="log4j" rev="1.2.14" conf="build, runtime" />
        <dependency org="org.apache.activemq" name="activemq-all" rev="5.1.0" conf="build, runtime" />
        <dependency org="commons-lang" name="commons-lang" rev="2.4" conf="build, runtime" />
        <dependency org="commons-dbcp" name="commons-dbcp" rev="1.2.2" conf="build, runtime" />
        <dependency org="org.mortbay.jetty" name="jetty" rev="6.1.11" conf="build, runtime" />
        <dependency org="junit" name="junit" rev="4.5" conf="build" />
        <dependency org="mysql" name="mysql-connector-java" rev="5.1.6" conf="mysql, runtime"/>
        <dependency org="xerces" name="xercesImpl" rev="2.8.1" conf="runtime"/>
        <dependency org="commons-logging" name="commons-logging" rev="1.1.1" conf="runtime" />
        <dependency org="javax.jms" name="jms" rev="1.1" conf="runtime" />
        <dependency org="org.easymock" name="easymock" rev="2.4" conf="junit" />
        <dependency org="org.easymock" name="easymockclassextension" rev="2.4" conf="junit" />
        <dependency org="org.dbunit" name="dbunit" rev="2.3.0" conf="junit" />
        <dependency org="net.sourceforge.cobertura" name="cobertura" rev="1.9rc1" conf="cobertura, junit" />
        <dependency org="findbugs" name="findbugs" rev="1.3.5" conf="findbugs, build" />
    </dependencies>
</ivy-module>
Lets start with the dependencies first.  Each dependancy has an org, name, rev and an optional conf.  The org, name and rev correspond to the organization, module, and revision in the ivysettings.xml as you might assume.  For your common jars you can find the necessary values by looking up the package at mvnrepository.com of course they call org group, module artifact, and revision version, just to keep you on your toes.  Additionally you should notice the defaultconfmapping=”*->default”, if you forget this things blow up quick and in weird ways. The dependancy thing is pretty sweet but the real power comes when you start in on the configurations.  As you can see I have defined six configurations that correspond to different sets of jars that I need to reference in my build.xml.  Configurations can inherit from eachother using the extends so junit is a superset of build, runtime, and the junit specific jars.  Assigning jars to configurations is as easy as listing the right configurations in the conf attribute of the dependancy tag.
Next we tie this all together in the build.xml.
    <target name="dl-binaries" depends="checkfiles, dl-findbugs, dl-ivy" description="download runtime binaries to external"/>

    <target name="checkfiles" description="checks if files have already been downloaded">
        <available file="${findbugs.dir}" type="dir" property="findbugs.exists" />
        <available file="${ivy.jar.file}" property="ivy.exists" />
    </target>

    <target name="dl-findbugs" description="download findbugs" depends="checkfiles" unless="findbugs.exists">
        <get src="${findbugs.url}" dest="${findbugs.zip}" usetimestamp="true" />
        <unzip src="${findbugs.zip}" dest="${external_home}" />
        <delete file="${findbugs.zip}"/>
    </target>

    <target name="dl-ivy" description="download ivy" depends="checkfiles" unless="ivy.exists">
	<mkdir dir="${ivy.jar.dir}"/>
        <get src="http://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
            dest="${ivy.jar.file}" usetimestamp="true"/>
    </target>

    <target name="ivy" depends="dl-ivy" description="configure ivy" >
    	<path id="ivy.lib.path">
            <fileset dir="${ivy.jar.dir}" includes="*.jar"/>
        </path>
        <taskdef resource="org/apache/ivy/ant/antlib.xml"
              uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
        <ivy:settings file="ivysettings.xml" />
    	<ivy:retrieve />

        <!-- Define Paths -->
        <ivy:cachepath pathid="build.path" conf="build" />
        <ivy:cachepath pathid="runtime.path" conf="runtime" />
        <ivy:cachepath pathid="junit.path" conf="junit" />
        <ivy:cachepath pathid="cobertura.path" conf="cobertura" />
        <ivy:cachepath pathid="mysql.path" conf="mysql" />
    	<ivy:cachepath pathid="findbugs.path" conf="findbugs" />
    </target>
To start off we have the ivy target which takes care of a few things.  First it depends on dl-ivy for bootstrap downloading of ivy.  Once Ivy is downloaded it sets up the ivy path, defines the ivy ant tasks, and reads the settings file.  At this point all the magic happens with the <ivy:retrieve /> tag.  This goes out, reads the ivy.xml and grabs all the necessary jars.  Once that is done we can create ant paths for all the different configurations we defined in ivy.xml.  This will become super useful later on.  Another thing to notice here is downloading of findbugs.  This is done for two reasons.  First this is a good example of downloading 3rd party libs using ant that are not in a maven style repo.  This target goes out and gets findbugs, unzips it and then ivy can use it through the local filesystem resolver in the ivysettings.xml. The other reason it is here is that the default way of downloading findbugs through the maven style repo conflicts horribly with the findbugs ant plugin.  The plugin expects the files in a very specific layout which is broken when you download from maven vs downloading it from their site.  I use this idiom for downloading some other picky 3rd party jars as well as some binaries that are useful for development but are not actually needed for java.
Finally we get to see the power of these configurations.
     <target name="compile" depends="init,messages,dbobjs" description="compile all the java source">
       <javac target="${TARGET}" source="${SOURCE}" debug="${DEBUG}" destdir="${build}" includes="**/*.java">
            <src path="${src}"/>
            <classpath>
                   <path refid="build.path" />
            </classpath>
            <!-- <compilerarg value="-Xlint"/> -->
        </javac>
    </target>
All we have to do is add the build.path reference and away we go.  We can do the same for findbugs, mysql, junit, adding runtime dependancies into a jar that we create in our build and so on.  Need to add a new jar?  Look it up in the maven repo, add it to ivy.xml and define it as a build, runtime, junit, etc dependancy and away you go.  Pretty slick!
Follow

Get every new post delivered to your Inbox.