Help

Dan Allen is the author of Seam in Action, the comprehensive guide to the Seam framework. He is also a Seam committer and passionate open source advocate specializing in Java EE frameworks and technologies.

Location: Laurel, MD
Occupation: Java EE Software Consultant
Archive
Using a seam-gen project in JBoss Tools
03. Nov 2008, 06:29 CET, by Dan Allen

The Seam distribution includes a tool named seam-gen to help you get started quickly using Seam. The tool collects information from you about your project and then uses that information to create a project structure. It can also generate a CRUD application by reverse engineering your database schema and generate various forms of stub code. In this entry, you'll learn how to get the generated project setup in Eclipse so that you can leverage the available tooling.

Two tools, one goal

There are actually two interfaces to seam-gen. The first is the commandline script named seam, which resides at the root of the Seam distribution. When we talk about seam-gen, we are usually referring to this script. There is also new project wizard in JBoss Tools (the open source project behind JBoss Developer Studio) that is a complement to this script. Both tools share the same FreeMarker templates inside the seam-gen directory of the Seam distribution to produce the Java code, Facelets views, and TestNG configuration. Aside from these common templates, the two tools work independently of one another in setting up a Seam project. The projects they generate differ in one fundamental way, though. The commandline tool produces projects that are built using Ant, while the JBoss Tools new project wizard creates projects that are build purely with Eclipse builders.

As you'd expect, when you create a project using the Eclipse plug-in, you can immediately start taking advantage of the tooling support for Seam in your project. However, like me, you may find it important to have a project that can be built outside of Eclipse using Ant. That means creating the project using the seam-gen commandline script. Don't fear that by creating the project outside of Eclipse, that the IDE is reduced to a syntax checker and Java compiler. The purpose of this article is to demonstrate how to fully activate the Seam tooling in Eclipse for projects created using the seam-gen commandline script, bringing you the best of both worlds. Plus, once the Seam tooling is activated, you can generate the CRUD application or stub code from either Eclipse or the commandline script.

Step 1. Create the project with seam-gen (i.e., the commandline script)

There are plenty of tutorials available for how to use seam-gen to create a project. You can find one in the Seam reference guide and another in chapter 2 of Seam in Action. Let's just quickly review the steps for completeness.

  1. Download and extract the Seam 2.x distribution
  2. If on Linux or Mac, run
    chmod 755 seam
    from the root of the distribution to make the seam script executable
  3. Make sure the JAVA_HOME environment variable is defined and it points to a JDK installation (>= 1.5)
  4. In a terminal window, change your working directory to the Seam distribution and execute seam setup on Windows or ./seam setup on Linux or Mac (optionally, you can put the Seam distribution in your PATH to use seam like a system command)
  5. Execute the seam script again, this time supplying it the create-project command

Your project is now created. The next step is to get the JBoss Tools plug-in for Eclipse installed so that you can use the Seam tooling once you get the project imported into Eclipse.

Step 2. Install the JBoss Tools plug-in for Eclipse

If you are using JBoss Developer Studio, you can skip this step. JBoss Developer Studio comes with all the plug-ins you need to develop Seam applications (plus support). These instructions explain how to install the latest stable version of JBoss Tools, which should be used with Eclipse 3.3 (Europa). You can also install the development version, which works with Eclipse 3.4 (Ganymede).

  1. Download the Eclipse IDE for Java EE Developers via eclipse.org. JBoss Tools relies on the Eclipse Web Tools project and several other plug-ins that come with that distribution.
  2. Select the menu item Help > Software Updates > Find and Install...
  3. Choose the option Search for new features to install
  4. Click the button New Remote Site...
  5. Enter the following values in the form:
    Name: JBoss Tools Stable Updates
    URL: http://download.jboss.org/jbosstools/updates/stable
  6. Click Finish
  7. Check the option JBoss Tools Stable Updates and advance through the rest of the wizard
  8. Go get coffee while the plug-ins download
  9. Click Install All when the download completes and allow Eclipse to restart when the install is done

JBoss Tools is now installed. If you are interested in seeing other ways to install JBoss Tools, refer to this page on the JBoss Wiki.

Step 3. Import the project into Eclipse

When seam-gen creates a project, it also generates Eclipse project files. Therefore, Eclipse will recognize the project directory as a valid Eclipse project. You simply need to use the Eclipse project import wizard.

  1. In Eclipse, select File > Import project... and choose the Existing project into Workspace option

  2. Browse to the location of the generated project and Eclipse should recognize the project

  3. Click Finish

As soon as Eclipse imports the project you should notice output from Ant in the Console view. As I mentioned earlier, projects created with seam-gen use Ant to build and Eclipse drives those Ant tasks.

You're seam-gen project is now a JBoss Tools Seam project! That means you can take advantage of the core Seam tooling. However, you cannot yet use the code generation. You need to inform JBoss Tools where your Seam runtime is in order to do things such as create a new Seam action.

Step 4. Attach the project to the Seam runtime

JBoss Tools needs to know where the Seam distribution is because that is where the shared code generation templates are that I mentioned earlier. To make that connection, right click on the root node of the project and select Properties. Select the Seam Settings in the sidebar of the window that appears to bring up the Seam settings properties sheet as shown here.

To fully activate the Seam support, you need to create a Seam runtime.

  1. Click on the Add... button to open the dialog for defining a new runtime
  2. Browse to the location of the Seam distribution where you created the project, choose a name for the runtime, and select version 2.0.

  3. When you are done, click Finish

You should see that most of the form fields in the Seam settings property sheet are now active. However, you still need to establish a (database) connection profile.

  1. Click New... to open the dialog to define a new connection
  2. Select a connection type (HSQLDB or Generic JDBC)
  3. Progress through the wizard using the same connection information you used when creating the project

The Seam property sheet should now be fully populated, as shown here:

You will now be able to use all of the code generation tasks in JBoss Tools without having to go back to the commandline to run the seam script. For instance, if you select File > Other... > Seam Generate Entities, you can create a CRUD application by reverse engineering the database defined in the Hibernate console configuration. (The latest version of the Seam tools even offers selective generation of entities).

Once the code generation completes, you can open one of the Seam components by name using the Open Seam Component dialog:

JBoss Tools also provides EL completion, such as in a Facelets view:

You can also use the Hibernate Console, the Seam page descriptor editor, the Seam component descriptor editor, and so on. In fact, for the most part, you are able to work with your seam-gen project in JBoss Tools as if you had created the project using the Seam Web Project wizard in JBoss Tools. However, as of Seam 2.1, one features that you lose when you start with the commandline version of seam-gen is incremental deployment of the application through the Eclipse tooling.

Step 5. Deploy the application to JBoss AS

In order to deploy a seam-gen application to JBoss AS, you have to use the Ant targets in the project build file, even when developing in Eclipse. But you can drive those Ant tasks from Eclipse.

  1. Choose Window > Show View > Ant to bring up the Ant view
  2. Right click in the Ant view and select Add buildfiles...
  3. Select the build.xml at the root of the project
  4. When the Ant tree appears, expand it and scroll down to the explode target.

  5. Double click on the explode target to deploy the application to JBoss AS as an exploded archive

You can start JBoss AS either from the commandline or by setting up a JBoss AS runtime in Eclipse. However, as I mentioned in the last section, JBoss Tools cannot natively deploy a project created by seam-gen to the JBoss AS runtime at the moment. We are working on improving seam-gen to setup the Eclipse project so that the native incremental deployment works out of the box. Until then, you can still take advantage of incremental hot deployment because the explode Ant task is tied to the auto-build mechanism in Eclipse.

Step 6. Debug the application

If you start JBoss AS in debug mode, you can attach the Eclipse debugger to it by clicking on arrow next to the debug icon in the main toolbar and selecting debug-jboss-<projectName>, where <projectName> is the name you assigned to the project. (Close and open the project again if you don't see this option).

The debugger connects to JBoss AS as a Remote Java Application.

IntelliJ IDEA Sneak Peak

If you checkout Seam from SVN and use the seam script to create a new project, you can import that project into IntelliJ IDEA and take advantage of the Seam tooling provided in IntelliJ IDEA 8. IntelliJ IDEA 8 offers wide range of Seam tooling support, including EL completion, validation, and refactoring. You can check out a screencast of the new features here.

Summary

This tutorial demonstrated that you are able to use the Seam tooling provided by JBoss Tools for projects created using seam-gen. You learned how to import the project into Eclipse, setup a Seam runtime, configure a connection profile, and deploy and debug the application on JBoss AS. You also got a little sneak peak at the parallel tooling support for Seam in IntelliJ IDEA 8.

seam-gen gets a modest upgrade
19. Oct 2008, 08:35 CET, by Dan Allen

Along with Seam 2.1 comes a handful of enhancements to seam-gen. These changes are a culmination of the mods I made to the seam-gen project that forms the basis of the sample code for Seam in Action. Perhaps after reading this entry, you'll conclude that the enhancements go well beyond modest.

The improvements to seam-gen can be classified into four categories:

  • look and feel
  • functional
  • project structure / build
  • seam script

Since we're all attracted to shiny new things, let's start with the style upgrades and supplement the discussion with some screenshots.

Freshening up seam-gen's look

Both RichFaces and ICEfaces seam-gen projects already carry a touch of elegance thanks to the component styling controlled via CSS-based themes (i.e., skins) bundled with the two libraries. The styles that get imported with a theme are most apparent in the rich layout components from the JSF component sets (e.g., <rich:panel> and <ice:panelTab>). But there are some areas of the page that don't get coverage, such as the form elements and status messages. I figured out a way to stretch the theme across the whole page using RichFaces' theme extensions and some better default styles (unfortunately, the theme extensions only apply to RichFaces projects at this point).

Although it may seem trivial to worry about how the application looks, these style improvements are an important first step to ensuring that your application looks more presentable down the road (giving you one less thing to worry about early on in the project).

Extending the RichFaces skin

RichFaces uses skins to decorate its built-in components. But what's neat is that this skinnability feature is left entirely open to extension. A skin is nothing more than a message bundle that maps colors and font sizes to key names (e.g., panelBorderColor). Each rich component (e.g., <rich:dataTable>) is bundled with dynamically generated CSS that references these key names to style the component according to the theme.

The CSS is generated from an XML-based CSS file, which uses the file extension .xcss. The syntax used in this file consists of both standard and special skin-aware elements and attributes that translate into CSS. To extend the theme, you simply create a custom .xcss file and import it into your page using <a:loadStyle> from the Ajax4jsf component set. If you need to customize the colors and font sizes, or even add an additional skin property, you simply create a file named %SKIN_NAME%.skin.properties and place it at the root of the classpath. You replace %SKIN_NAME% with the name of the RichFaces skin (e.g., emeraldTown).

Details, details, details. Where's the benefit? Well, through the power of CSS, we can make non-RichFaces components look rich! And it's not just about colors, either. It's even possible to generate gradient images using one of the special elements in xcss (<f:resource>). That means we can make slick looking form elements. In seam-gen projects, these additional theme-related styles are defined in the file theme.xcss, which sits adjacent to the existing theme.css file. Here's a snippet from that file which styles text inputs:

<u:selector name="input[type=text], input[type=password], textarea, select">
  <u:style name="background-color" skin="controlBackgroundColor"/>
  <u:style name="color" skin="controlTextColor"/>
  <u:style name="background-position" value="left top"/>
  <u:style name="background-repeat" value="repeat-x"/>
  <u:style name="background-image">
    <f:resource f:key="org.richfaces.renderkit.html.images.SliderFieldGradient"/>
  </u:style>
  <u:style name="border" value="1px solid"/>
  <u:style name="border-color" skin="tableBorderColor" />
</u:selector>

Here's the CSS that this XML markup produces (for the DEFAULT RichFaces theme):

input[type="text"], input[type="password"], textarea, select {
  background-color:#FFFFFF;
  background-image:url(/appname/a4j/g/3_2_2.GAorg.richfaces...SliderFieldGradient...);
  background-position:left top;
  background-repeat:repeat-x;
  border:1px solid #C0C0C0;
  color:#000000;
}

And here's how an input generated by a plain old <h:inputText> would look:

Keep in mind that the input's style is going to coordinate with the RichFaces theme. That's true of all form elements as well as the general font color and the color of links. See for yourself in the next section.

Showing off the new look

Below is a collection of screenshots, which are taken from a seam-gen project generated from the vehicles schema used for testing seam-gen. These screenshots show off the new styles as they look with various RichFaces themes. The RichFaces theme is controlled using the org.richfaces.SKIN servlet context parameter defined in web.xml:

<context-param>
  <param-name>org.richfaces.SKIN</param-name>
  <param-value>classic</param-value>
</context-param>

The first thing you'll notice when you deploy a seam-gen 2.1 project is that there is a new home page featuring the Seam logo. Beyond that, you should simply notice that the elements on each page below all coordinate with the selected theme.

You'll notice in these next two screenshots that I have customized the status messages, also true for ICEfaces projects. An appropriate icon is used in front of each status message according to its level (warn, error, and info). While working on this enhancement, I fixed JBSEAM-1517 so that the messages look presentable and don't overlap. (Believe it or not, the messages were still using the orange style from the original seam-gen, which didn't match at all with the RichFaces or ICEfaces theme).

The cherry on top of all this is the favicon of the Seam logo, which is defined in the main Facelets template and appears in the URL location bar. I think this is the sweetest enhancement because it gives you that pride that you are using Seam.

The only downside of the new theme is that the Visual Editor in JBossTools doesn't understand the xcss file. That's why I left behind an improved version of the theme.css file, which is still used in the main Facelets template. In it, I define static CSS styles that don't rely on the skin properties. It certainly would be cool if the Visual Editor could interpret the xcss file as it would make developers more productive when extending the RichFaces theme. Currently, after making a change to the xcss file, you have to do a hard restart of the application to get RichFaces to reload the generated CSS (and with some browsers you also have to clear out the browser's cache).

That's enough about styles for now. There are a couple other enhancements to the application's presentation that fall more along the lines of functional, so we'll look at those next.

Just what the boss ordered

As I talk about enhancements to seam-gen, it's important to keep in mind the purpose of seam-gen so we don't lose perspective. seam-gen provides two key services:

  1. A starting point for developing with Seam, saving you from getting started tedium
  2. A broad demonstration of Seam features that you can begin building on

seam-gen is not going to do your job for you. With that said, there were a lot of areas of inconsistency in the generated project and other things that seam-gen could do better. Here's a list of functional changes that were made to seam-gen projects, which were mostly developed to satisfy requests that trickled down from management:

  • Human-readable labels are generated from property names using a camel-case translation (i.e., camelCase => Camel case)
  • The proper converter is applied whenever outputting the value of a property (e.g., date, time, number)
  • Icons are used in the tabs on the detail pages to denote parent and child associations
  • The markup for the sort links in the column headers on the listing pages is simplified using a Facelets composition template
    <ui:include src="layout/sort.xhtml">
      <ui:param name="entityList" value="#{personList}"/>
      <ui:param name="propertyLabel" value="Name"/>
      <ui:param name="propertyPath" value="person.name"/>
    </ui:include>
  • Client-side sorting of columns in the child association table is enabled on the detail pages (leveraging the RichFaces table)
  • Activated sorting on the listing page for properties on a component or composite key (e.g., vehicle.id.state)
  • JPQL used for sorting is now compliant
  • Added a link in each row on the listing page to edit the record (previously only possible from detail page)
  • Changed the default username to admin, still allowing any value for the password. The failure login scenario can now be triggered out of the box.
  • One-to-one associations are linked up in the UI the same way as many-to-one associations
  • Introduced a head named insertion in the main Facelets template (view/layout/template.xhtml) for adding additional markup to the <head> element on a per-page basis
    
    <ui:define name="head">
      <script type="text/javascript">
        window.onload = function() { ... }
      </script>
    </ui:define>
    
  • Moved the <h:messages globalOnly="true"> declaration to the main Facelets template and conditionally check a Facelets parameter named showGlobalMessages to turn them off on a per-page basis
  • The conversation is not propagated by links in the main menu bar (i.e., propagation="none")
  • Added a button to reset the search on the list pages
  • The entity manager is switched to manual flush mode when editing a record
  • A UI command link was added to to the debug page for destroying a selected conversation (not specific to seam-gen)

I'm sure there are other small enhancements that I am forgetting about, which I will leave for you to discover. Let's move on to the project structure and build changes.

Semantically speaking

seam-gen projects are still built using Ant and they still have fundamentally the same (quirky) structure. What I did, though, was fix a semantic error. I changed the source directories so they better reflect how they are built rather than what classes they contain. In Seam 2.0, the two main source directories were:

  • src/action
  • src/model

Components in src/action were added to hot deployable classpath when running in development mode, whereas components and regular classes in the src/model directory ended up on the main (static) classpath. In production mode, of course, the resources in these two directories both would end up in the same classpath. The problem is that hot deployable components are not necessarily action components and non-hot deployable components are not necessarily model classes. To fix this semantic mixup, the source directories are now:

  • src/hot
  • src/main

I trust that you can immediately grasp the purpose of these two source directories and thus feel more comfortable finding a place for the class you are about to create. Unfortunately, JBossTools still uses the old convention for the time being. Despite the difference, it has no problem consuming a seam-gen project with the new folder names since, internally, it relies on a mapping between folder name and function (seam-gen prepares the mapping configuration to reflect this new convention).

Naturally, the project build script was updated to accommodate the new names for these source directories. But there are several other changes that came out of my meddling with the build.

More muscle under the hood

First and foremost, tests which extend SeamTest can now be run using Java 6! This was a huge frustration for me and I am thrilled to have worked out a solution. The issue is really that the Embedded JBoss container is sorely out of date and flakes out on Java 6. Until a newer version of Embedded JBoss is ready, the workaround is to use and endorsed JAR file to fix a conflict with JAX-WS and to allow a special array syntax specific to the Sun JDK. You can check the test target in the build file for the specifics.

Fans of Groovy will be excited to learn that it's now possible to use Groovy components in EAR projects. The EAR project build compiles Groovy scripts on the classpath using groovyc, making them bona fide Java bytecode. (Getting seam-gen EAR projects to interpret .groovy files at runtime requires some more research). But what's even more exciting is that you can now write tests in Groovy (again compiled using groovyc) and test components written in Groovy. Really, this was just a matter of putting the build logic in place, but it's still important to know that you can get the Groovy support right out of the box. There's still work to be done to make the support for Groovy more elegant, but for now it is at least comprehensive.

Here's a bunch of other goodies that were thrown into the build:

  • seam-gen WAR projects now have a consolidated compile target
  • two new targets, reexplode and redeploy, execute clean unexplode explode or clean undeploy deploy, respectively
  • the restart target now detects if you have deployed an exploded or packaged archive and restarts the application appropriately (before it bombed if you had a packaged archive deployed)
  • the view folder is added to the classpath during the execution of tests so that page actions defined in fine-grained page descriptors (i.e., .page.xml) get invoked properly (previously tests only read the global page descriptor (i.e., pages.xml)
  • a new target named javadoc generates JavaDoc API from the project source

If you are interested in getting the dependencies of a seam-gen project under control, check out my article about how to manage the dependencies of a seam-gen project with Ivy.

That wraps up just about all the changes to seam-gen projects. All in all, what these upgrades should do is make your demos of Seam look just a touch prettier and give you more hammers to swing during development. Obviously, there are still plenty of improvements that could be made, so feel free to contribute them. Patches generated with svn diff are preferred.

In the final section, I want to talk about the changes that were made to the seam script and the different ways that it can now be invoked.

Removing the shackles from seam-gen

The seam script (which we all call seam-gen) has finally been set free. In the past, the seam script hasn't been as comfortable to use as other project generation tools, such as Grails, because the developer has been locked into having to execute it from a Seam distribution directory. The approach that Grails takes is to add the location of the grails script to your PATH, set GRAILS_HOME, and use the script like a system command. I have an approach that falls somewhere in between and that I believe is more flexible.

Thanks to changes in the bootstrap script, you can now run seam out of any directory on the computer and it will work as it does today (meaning your current working directory can be anywhere). The seam script still has to reside in a Seam distribution directory or the SEAM_HOME environment variable must be set if it doesn't, but now you have a third option of creating a symlink to the seam script from a directory already on your path (such as ~/bin). Internally, the script figures out your current directory and the SEAM_HOME environment variable relative to the location of the script (there is no reason to make the developer set the SEAM_HOME environment variable, though it's still honored if present). More on the current working directory in a second. The point so far is that the seam script is going to work from anywhere. No more silly restrictions.

As a side note, I want to mention that I added a validation check in the bootstrap routine that verifies the user has the JAVA_HOME environment variable defined and that it resolves to a valid JDK installation. Otherwise, the Ant build is going to fail anyway.

Ever since Seam 2.0, the seam create-project command has been moving the build.properties that holds the responses from seam setup to the file seam-gen.properties in the root of the project. But we haven't done anything with this file yet...until now. Here's the big enhancement (the one you have all been asking and waiting for).

If your current working directory has a seam-gen.properties file AND and build.xml file, then the seam script recognizes that you are in a seam-gen project and operates using the seam-gen.properties file in that directory rather than the seam-gen/build.properties in the Seam distribution. If you put the seam script (or a symlink to the seam script) on your PATH, then you can simply enter a project and run seam generate. If the seam script is not on your PATH, then you have to run something like ~/projects/seam-2.1.0.GA/seam generate. It really doesn't matter how it is invoked. This works across the board on Linux, Windows, and Mac. (By the way, writing cross platform scripts is a huge pain).

When your current directory is a seam-gen project, you essentially have the choice of using the seam script or the ant command to run the targets. The seam script is beneficial if the ant command is not on your PATH, though if you have Ant installed, then the seam script is just a middleman. That is, unless you are executing a code generation target (e.g., new-form, generate). Then you do need to use the seam script.

So there you have it, you can now use seam-gen to manage multiple projects and you can execute code generation from within the project! But wait, there is even more! If you don't yet have a seam-gen project created, you can feed a properties file of your choice into the script using the -Dseam-gen.properties flag:

seam -Dseam-gen.properties=seam-gen/myproject-build.properties

The value of the property must either be an absolute file path or a path relative to the root of the Seam distribution. You can also override the location of the seam-gen directory or even just the seam-gen templates (if you keep a customized version) using -Dseam-gen.dir and -Dtemplates.dir, respectively. It may be a little obscure to have to pass -D flags to the seam script, but it gives you the power you are looking for.

I hope you're excited about all the enhancements that I presented in this article and that it helps you to become more productive with your seam-gen projects. There is still work to be done, including a major refactoring of the seam-gen build system, which I am going to start documenting on this wiki page. But for now, you should have plenty of new toys to play with.

Managing the dependencies of a seam-gen project with Ivy
03. Oct 2008, 03:23 CET, by Dan Allen

Out of the box, seam-gen copies all the dependencies of Seam (and then some) into the lib directory of the generated project. The JAR files in that directory are then placed on the project's build path. While this approach gets you up and running quickly, it's probably not the best long-term strategy. It's difficult to determine which libraries your project actually depends on and which versions of those libraries are present. What you need is some sort of formal dependency management.

One way of managing dependencies is to use Maven 2 as the build tool, which several people have requested for seam-gen to support. However, shifting to Maven 2 is quite a large departure from the current build, which is based on Ant. In this article, I will explain how you can introduce Ivy into the Ant build to track which dependencies and versions of those dependencies your project uses and to have them fetched from a remote repository. Ivy is a sub-project of Ant and provides a collection of Ant tasks focused specifically on providing a dependency manager for Java libraries.

About this solution

What makes the setup I present in this article unique is that it separates fetching the dependencies from the build itself, so once the JARs are in place, the build can work just as it always has. That means the use of Ivy does not affect the efficiency of development that a seam-gen project provides. You also have the ability to create a distribution of the project which includes all the dependent JAR files, a key aspect of ensuring reproducibility. But there is something else that sets this solution apart.

Somewhere along the line folks seemed to have coupled shared repositories with transitive dependencies. You'll discover that it's possible to pull artifacts from the remote repositories without having to use the transitive dependency mechanism common to all Maven 2 projects (and an optional feature in Ivy).

Making way for Ivy

The first step to integrating Ivy into the project is to create a separate Ant build file, which we'll name ivy.build.xml, to host the Ivy-related tasks. Isolating these tasks in a separate file makes them more reusable and keeps the main build file clean. The Ivy build file is imported near the top of the main build file using the Ant import task:

<import file="${basedir}/ivy.build.xml"/>

In the Ivy build file, the first tasks you'll create are ones that fetch Ivy itself, in a sort of self-updating way. We'll store the Ivy JAR file in the build-lib directory at the project root to keep it isolated from the application's dependencies. To be efficient, we'll also check to see if the Ivy JAR has already been downloaded before attempting to fetch it. Here are the contents of ivy.build.xml we have talked about so far:

<?xml version="1.0"?>
<project basedir="." xmlns:ivy="antlib:org.apache.ivy.ant" name="myproject-ivy">
    <property name="ivy.install.version" value="2.0.0-beta2"/>
    <property name="ivy.jar.dir" value="${basedir}/build-lib"/>
    <property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar"/>
    <property name="central.repo" value="http://repo1.maven.org/maven2"/>
    <property name="jboss.repo" value="http://repository.jboss.org/maven2"/>

    <target name="init-ivy">
        <available property="ivy.installed" value="true" file="${ivy.jar.file}" type="file"/>
    </target>

    <target name="download-ivy" depends="init-ivy" unless="ivy.installed">
        <mkdir dir="${ivy.jar.dir}"/>
        <echo message="Installing Ivy..."/>
        <get src="${central.repo}/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
            dest="${ivy.jar.file}"/>
    </target>
<project>
NOTE I'm using Ivy 2.0.0-beta2 because there is a bug in 2.0.0-rc2 (the latest version at the time of this writing).

Before you can use Ivy, you need to have the Ivy configuration files in place. The first configuration file we will look at is the main settings file, which hosts the definitions for the artifact resolvers.

Chaining together a set of resolvers

Ivy works by mooching off of the Maven 2 repositories. However, unlike Maven, it can accommodate any hosted structure. So we need to tell Ivy where to look to find artifacts (i.e., dependencies) and what pattern it should use to locate an artifact in the remote repository as well as in the local cache. This configuration is defined in an Ivy settings file, which we will name ivy.settings.xml. We'll define three resolvers: local, central, and jboss, which we'll string together as a resolver chain and make it the default strategy.

<?xml version="1.0" encoding="UTF-8"?>
<ivysettings>
    <settings defaultResolver="default"/>
    <caches artifactPattern="[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]" 
        checkUpToDate="true"/>
    <resolvers>
        <filesystem name="local">
            <ivy pattern="${ivy.cache.dir}/[module]/ivy-[revision].xml"/>
            <artifact pattern="${ivy.cache.dir}/[module]/[artifact]-[revision](-[classifier]).[ext]"/>
        </filesystem>
        <ibiblio name="central" m2compatible="true" usepoms="false" root="${central.repo}"
            pattern="[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"/>
        <ibiblio name="jboss" m2compatible="true" usepoms="false" root="${jboss.repo}"
            pattern="[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"/>
        <chain name="default" returnFirst="true">
            <resolver ref="local"/>
            <resolver ref="central"/>
            <resolver ref="jboss"/>
        </chain>
    </resolvers>
</ivysettings>

As I mentioned earlier, Ivy can grab JARs from almost any repository, but that also means that you have to tell it what type of repository it is dealing with. The <ibiblio> tag in the Ivy settings file applies a built-in pattern that allows Ivy to treat the repository as a standard Maven 2 repository. However, there is a bug in that pattern that makes it unable to handle non-binary artifacts, so we have to define the pattern explicitly.

You'll notice in this XML configuration two different styles of placeholder variables. The first are regular Ant property references. You can use any property you define in your Ant build prior to calling an Ivy task or any implicit property that Ivy defines (these are not well documented). As you can see here, we are using the built-in ivy.cache.dir property reference to define where the JAR files get cached locally, which happens to resolve to ${user.home}/.ivy2/cache.

The second type of placeholder variables, which are anchored by square brackets, are special Ivy replacement tokens that resolve to the segments of the artifact. The following table shows how these tokens map to Maven 2 terminology:

IvyMaven 2
organisationgroup ID
module, artifactartifact ID
revisionversion
exttype
classifierclassifier

The classifier took me a long time to discover and it requires a special setup to use in Ivy. More on that later.

Once Ivy resolves a dependency, it copies the artifact into the local cache directory, similar to how Maven 2 works. However, you have more control at the configuration level with regard to how Ivy handles updates. You also have the ability to segment the cache by project, which is done by adjusting the patterns in the <filesystem> node. This allows you to keep unrelated projects from interfering with one another. On top of this flexibility, the Ivy cache is already easier to manage than the Maven 2 local repository since it doesn't pollute the cache with its own plugins.

With this configuration in place, Ivy will search the local cache, then the central repository, and finally the JBoss repository to locate an artifact. Now the question becomes, how do you define what Ivy needs to fetch? That's where the Ivy module file comes in.

Telling Ivy about your dependencies

The ivy module file, typically named ivy.xml (though its location can be changed using the ivy.dep.file configuration property), is akin to the dependencies section of a Maven 2 POM file. It's where you define the libraries on which your project depends. Fortunately, the Ivy team does believe in XML attributes, unlike the Maven 2 radicals, so you have a lot less typing to do to define a dependency. But the most absolutely vital feature of Ivy, and what makes it infinitely more useful than Maven 2, is the fact that you can disable transitive dependencies.

I'm outspoken about my position on transitive dependencies. I see them as both evil and a silly device designed for novices (and people with way too much time on their hands). It makes your build non-reproducible and unstable and in the end causes you more work than the work you were attempting to eliminate by moving to Maven 2. This feature really is Maven's boat anchor. Trust me on this one, it's trivial to define which libraries your application depends on. There really aren't that many! And you can put all the worrying aside about exclusions. Okay, enough ranting and time to get back to the task at hand. I could go on about this topic all day!

Below is an Ivy module file for a vanilla seam-gen 2.0.3.CR1 WAR project with a couple of extras thrown in (for now I have removed the dependencies for Drools). Although Ivy has the awareness of dependency scopes (e.g., compile, runtime, test), we aren't concerned about that feature because all we are looking to do is inflate the project lib directory with the JARs we need to use the Ant build as-is.

<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="1.0">
    <info organisation="org.example" module="myproject"/>
    <configurations>
        <conf name="default" transitive="false"/>
    </configurations>
    <dependencies defaultconf="default">
        <dependency org="com.sun.facelets" name="jsf-facelets" rev="1.1.15.B1"/>
        <dependency org="commons-beanutils" name="commons-beanutils" rev="1.7.0"/>
        <dependency org="commons-digester" name="commons-digester" rev="1.7"/>
        <dependency org="javax.el" name="el-api" rev="1.0"/>
        <dependency org="javax.faces" name="jsf-api" rev="1.2_04-p02"/>
        <dependency org="javax.faces" name="jsf-impl" rev="1.2_04-p02"/>
        <dependency org="javax.persistence" name="persistence-api" rev="1.0"/>
        <dependency org="javax.servlet" name="servlet-api" rev="2.5"/>
        <dependency org="javax.transaction" name="jta" rev="1.0.1B"/>
        <dependency org="org.codehaus.groovy" name="groovy-all" rev="1.5.4"/>
        <dependency org="org.hibernate" name="hibernate-validator" rev="3.0.0.GA"/>
        <dependency org="org.jboss.el" name="jboss-el" rev="1.0_02.CR2"/>
        <dependency org="org.jboss.seam" name="jboss-seam" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-debug" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-ioc" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-mail" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-pdf" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-remoting" rev="2.0.3.CR1"/>
        <dependency org="org.jboss.seam" name="jboss-seam-ui" rev="2.0.3.CR1"/>
        <dependency org="org.jbpm" name="jbpm-jpdl" rev="3.2.2"/>
        <dependency org="org.richfaces.framework" name="richfaces-api" rev="3.2.2.GA"/>
        <dependency org="org.richfaces.framework" name="richfaces-impl" rev="3.2.2.GA"/>
        <dependency org="org.richfaces.ui" name="richfaces-ui" rev="3.2.2.GA"/>
        <dependency org="org.testng" name="testng" rev="5.6"/>
    </dependencies>
</ivy-module>

You may be wondering if it's possible to eliminate the duplication in version numbers for the Seam, JSF, and RichFaces libraries (and perhaps others). I have good news for you. An Ivy module file understands Ant property references. So you can declare those versions in the ivy.build.xml file and thus have once central location to control them.

<property name="seam.version" value="2.0.3.CR1"/>
<property name="jsf.version" value="1.2_04-p02"/>
<property name="richfaces.version" value="3.2.2.GA"/>

You can now update your Ivy module file to use these property references. Here's the main Seam artifact now using an Ant property reference to manage the version:

<dependency org="org.jboss.seam" name="jboss-seam" rev="${seam.version}"/>

All we have to do now is tell Ivy to fetch the dependencies and copy them to the lib directory. For that, we switch back to our Ant build file with the Ivy-related targets.

Inflating the project

We'll create a task for inflating the lib file with the artifacts defined in the Ivy module file. This task needs to depend on a task that loads the Ivy Ant tasks and that task has to in turn depend on a task that fetches Ivy. Here are the targets:

<target name="load-ivy" depends="init-ivy,download-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="${basedir}/ivy.settings.xml"/>
</target>

<target name="inflate-core" depends="load-ivy">
    <ivy:retrieve pattern="${lib.dir}/[artifact].[ext]" type="jar"/>
</target>

The load-ivy target first defines the Ant tasks Ivy provides using the <taskdef> element. The Ivy tasks are available under the namespace ivy. Finally, the <ivy:settings> task bootstraps the Ivy configuration. In the inflate-core target, the <ivy:retrieve> task resolves the artifacts from the chain of repositories and copies them into the project lib directory (the lib.dir property is defined in the main Ant build file).

Again, there is a mix of Ant property references and Ivy replacement tokens to define where the JAR file is going to end up. We are mirroring the standard seam-gen setup by putting the JAR files needed for compilation into the lib directory and named without their version numbers. You can of course keep the version number by referencing the [revision] token in the pattern, but then you have to update the deployed-jars.war file to ensure that the build includes the artifacts in the deployable archive. Here's a portion of the output when the inflate-core task is run:

Buildfile: build.xml

init-ivy:

download-ivy:

load-ivy:
[ivy:settings] :: Ivy 2.0.0-beta2 - 20080225093827 :: http://ant.apache.org/ivy/ ::
[ivy:settings] :: loading settings :: file = /home/dallen/projects/myproject/ivy.settings.xml

inflate-core:
[ivy:retrieve] :: resolving dependencies :: org.example#myproject;working@sandstone
[ivy:retrieve]  confs: [default]
[ivy:retrieve]  found com.sun.facelets#jsf-facelets;1.1.15.B1 in jboss
[ivy:retrieve]  found commons-beanutils#commons-beanutils;1.7.0 in central
...
[ivy:retrieve] :: resolution report :: resolve 1622ms :: artifacts dl 121ms
        ---------------------------------------------------------------------
        |                  |            modules            ||   artifacts   |
        |       conf       | number| search|dwnlded|evicted|| number|dwnlded|
        ---------------------------------------------------------------------
        |      default     |   24  |   0   |   0   |   0   ||   24  |   0   |
        ---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.example#myproject
[ivy:retrieve]  confs: [default]
[ivy:retrieve]  0 artifacts copied, 24 already retrieved (0kB/34ms)

BUILD SUCCESSFUL
Total time: 3 seconds

So far, so good, but currently we are running a bit short functionality that was present before we introduced Ivy. For one, we need the JBoss Embedded JAR files and dependencies in order to be able to run tests. The project build is expecting these artifacts to be in the lib/test directory. Additionally, seam-gen includes the source artifacts for Seam in the lib/src directory, so we will want to pull down those down from the repository as well (and any other sources you want to grab). The question is, where do these artifacts fit in?

Fetching auxiliary artifacts

It's time for us to expand our dependency list to itemize the different types of dependencies. We just identified three types:

  • jar
  • source
  • test-jar

However, the <dependency> element itself has no way to make these distinctions. That is the purpose of the nested <artifact> element. This element lets us define one or more types for a dependency. In addition, we can take on a couple of additional attributes. The most vital of those attributes is the classifier.

The classifier attribute ties back into a discussion I left off with earlier. In the Maven 2 repository, sources are named by appending the suffix -sources to the end of the file name, but before the file extension. The trouble is that this introduces a new segment to the pattern. (It would have been nice if binary artifacts in the Maven 2 repository used the suffix -binary, but that's just wishful thinking).

Fortunately, Ivy understands the concept of an optional segment. (I sure hope this tip saves you time because it took me the better part of a day to track down). An optional segment is defined in an Ivy pattern by surrounding it with parenthesis. Any other text which is between the parenthesis will be ignored if the token has no value. Going back to earlier, you might recall using the following pattern in ivy.settings.xml:

[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]

As you can see, the classifier will be appended to the URL if defined. What we need to do now is define what the classifier is for a given dependency. We'll unfold the <dependency> elements in the Ivy module file and insert one or more <artifact> elements, one of which will host the classifier attribute. Here, again, is the dependency declaration for the main Seam artifact, now paired with its sources:

<dependency org="org.jboss.seam" name="jboss-seam" rev="${seam.version}">
    <artifact name="jboss-seam" type="jar"/>
    <artifact name="jboss-seam" type="source" ext="jar" m:classifier="sources"/>
</dependency>

As you can see, the classifier attribute is defined in its own namespace. In order to prevent Ivy from complaining about the use of invalid XML syntax, you need to declare this namespace in the root element of the Ivy module file:

<ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
    ...
</ivy-module>

When Ivy resolves the dependencies, it grabs all the artifacts, regardless of type. But what the type discriminator allows us to do is copy the artifacts to different locations according to type. We'll introduce a new target in our Ant build that copies the source artifacts to lib/src, using the type in the file name to be consist with how seam-gen lays down the project files:

<target name="inflate-source" depends="load-ivy">
    <ivy:retrieve pattern="${lib.dir}/src/[artifact]-[type]s.[ext]" type="source"/>
</target>

All that we have left to do is grab the runtime test JARs. I'll admit to you that there may be a more elegant way to accomplish this task using Ivy dependency configurations, but I couldn't make sense of that feature and the approach I am about to suggest works perfectly (sometimes we forget that something that works is better than something that is supposed to work). Here are the dependency definitions for those test JARs:

<dependency org="org.jboss.seam.embedded" name="hibernate-all" rev="${jboss-embedded.version}">
    <artifact name="hibernate-all" type="test-jar" ext="jar"/>
</dependency>
<dependency org="org.jboss.seam.embedded" name="thirdparty-all" rev="${jboss-embedded.version}">
    <artifact name="thirdparty-all" type="test-jar" ext="jar"/>
</dependency>
<dependency org="org.jboss.seam.embedded" name="jboss-embedded-all" rev="${jboss-embedded.version}">
    <artifact name="jboss-embedded-all" type="test-jar" ext="jar"/>
</dependency>

Finally, here is the Ant target that retrieves the test JARs and another Ant target to retrieve all three types in a single commmand:

<target name="inflate-test" depends="load-ivy">
    <ivy:retrieve pattern="${lib.dir}/test/[artifact].[ext]" type="test-jar"/>
</target>

<target name="inflate" depends="inflate-core,inflate-source,inflate-test"/>

Though not shown here, you might also want to create a target to purge the lib folder and another to generate a dependency report using the <ivy:report> task.

Once you have all of the Ivy configurations in place described in this article, you can wipe our your lib directory and build your project in these two steps:

ant inflate
ant explode

You are now Ivy-enabled and you're lib folder is no longer bursting at the seams.

Summary

What we set out to do is reign in the chaos of the lib folder in seam-gen projects and setup a system for managing the dependencies. Having a dependency management solution in place makes it easy to add new libraries and upgrade existing ones. The approach taken in this article was to use Ivy to define the dependencies and retrieve them from a remote repository without having to change the project build in any way (other than to import the Ivy-related build file). I purposely did not attempt to integrate Ivy into the build process because there is really no need to constantly retrieve the artifacts while developing your application. Instead, what you want is a set it and forget it approach. This strategy has the side effect of making your build reproducible and stable.

If I get around to writing another article, the next step is to eliminate the deployed-jars.list file and figure out how to instead pull this information from the Ivy module file. But that will require understanding Ivy dependency configurations better on my part.

And in case you are wondering, yes, I am considering integrating Ivy support into the seam-gen core. It just seems like a no-brainer to me. I still have hope for Maven 2, but at this point I am seeing better productivity gains from using this Ivy configuration.

Get the complete Ivy configuration and build file[1] and start using Ivy to manage your application's dependencies today!