by Josef Betancourt

Let your Ant enjoy Spring

news
Feb 14, 200518 mins

Extend Ant using a lightweight Inversion of Control container

I needed to add a new task to an Ant-driven build and I implemented that task using Spring, a lightweight IoC (Inversion of Control) framework. I encountered virtually no issues since, because an IoC container is noninvasive, it is relatively easy to create a wrapper or just use the objects that implement the task. I then began to wonder if Ant could directly use Spring-configured objects, thus reusing the dependency graph and configuration already specified and tested. Why duplicate and introduce side effects or other problems? If IoC containers really do provide a benefit, a more direct use is warranted.

This article introduces this approach and presents a proof-of-concept implementation. Developers new to Ant extension coding should find this example interesting.

Ant extensions

To add a custom task to Ant, the Ant manual recommends using an Ant class intended for this purpose, like the Task class. But, even this recommendation is not mandatory; Ant can invoke any class that has an execute() method. (Of course, Ant can also invoke any program using the exec or java task, but that is a different type of extension.) Ant also supports aggregating these task extensions into various kinds of properties or XML files.

The best approach for adding a custom task to Ant is one that reuses the IoC framework via a Task extension. Thus, a Task that performs what a standalone application must do to set up and use the framework’s hosted objects and resources builds upon the strength of Ant.

Inversion of control

The Inversion of Control (IoC) design pattern, also called Dependency Injection (DI), in the framework context, is concerned with the componentization of Java objects. The growing interest in IoC frameworks is due in large part to the Spring framework’s developers showing the synergy available in an IoC/AOP/XML/JavaBeans lightweight framework, which offers capabilities beyond DI by allowing the creation of a powerful abstraction layer to other APIs or components. Spring is itself an example of the use of IoC. Also note that Ant seems conformant with this IoC container since it too is XML/JavaBeans-based and, in some ways, already uses IoC.

Requirements

Our Ant IoC task extension requirements can be specified in an actor/goal/requirements format (the requirements are not ordered):

  • Actor: Developer
  • Goal: Modify the IoC task
  • Requirements:
    • Run a regression test following any code changes or build
    • Easily add new tests to the regression set
    • Support a different IoC framework
    • Debug by changing Ant log level and/or IoC log configuration, and getting useful output
  • Actor: Build creator
  • Goal: Edit an Ant target and use the task to specify beans that are in or out of an IoC container
  • Requirements:
    • Set the IoC descriptor path
    • Optionally, if no container is required, specify FQCN (fully qualified class name) as target
    • If using IoC, set the POJO (plain old Java object) bean name, default is antBean
    • Specify the target method name, default is execute
    • Specify a method call expression that may have arguments
    • Specify properties to insert into the target bean, overriding container properties
    • Specify element text to push to target
    • No need to specify new classes to handle the Ant/IoC combination
    • Reuse existing property files for variable expansion
  • Actor: Task extension object
  • Goal: Invoke an object method
  • Requirements:
    • Invoke a POJO specified in the IoC bean definition
    • Invoke a specified class outside a container
    • Use default bean name antBean if none specified
    • Invoke a simple method, default is execute()
    • Invoke a method expression with optional arguments
    • If target is Ant-aware, insert project
    • Insert dynamic properties

Task

The task specification that supports these requirements is SpringContextTask.

Description

This task invokes a method on an object either managed by a Spring container or unmanaged and specified as a FQCN. Classpath Spring bean definition references are not supported yet.

SpringContextTask‘s parameters are shown in the table below.

Parameters

Attribute DescriptionRequired
beanLocations Path to IoC descriptorsOptional, or classname, not both.
className FQCN of target classOptional, or beanLocations.
beanName The bean id in the IoCOptional. Default: antBean.
methodName Method to invoke on beanOptional. Default: execute.
call Method call expressionOptional, or methodCall, not both.
   
*Dynamic attributes0 or more.
methodCall  Optional. Expression. A nested element. Alternative to the call attribute.

Examples

The simplest example of applying our new Ant task extension is:

 

<!-- create the task definition --> <taskdef name="runBean" classpathref="testpath" classname="jbetancourt.ant.task.spring.SpringContextTask"/>

<target name="simpleAppContextUseWithDefaults"> <runBean beanLocations="applicationContext.xml"></runBean> </target>

The simpleAppContextUseWithDefaults target invokes execute() on the bean named antBean in the bean definition applicationContext.xml found on the file path. The path attribute name is plural to support future use of multiple bean definition files.

This bean invocation resembles how Ant already invokes an object; however, this time, an IoC container manages the bean. The container could have added transaction dependencies, wired in datastores, set up a Web services proxy, used remoting, or even supplied an AOP (aspect-oriented programming) proxy instead of an actual target bean. Our approach simplifies configuration since the Ant script does not need to know how to configure an object, especially a complex one. But, what if the Ant script must set some required properties for a service call on a bean:

  <target name="publish">
      <spring 
         beanLocations="applicationContext.xml"
         beanName="siteGenerator"            
         methodName="generateSite"
         host="${host.site.url}" 
         port="${site.port}">
         Made a few tweaks.  Removed some sentence fragments.           
      </spring>                    
   </target> 

Note that since the task name is defined in a taskdef, the name used depends on the Ant taskdef definition (not shown); here, the task name is spring. Now we specify the bean name and a method to call. The element text is also pushed to the target bean. In this example, the text is a publish comment.

With the use of Ant’s dynamic attributes feature, we also push required properties to the target object. Usually when an attribute is parsed in an Ant file, a setter is called on the task class. With dynamic attributes, a non-object property or field is added to the object with the setDynamicAttribute() method. In essence, this property injection provides an override capability, since the container could have already wired in the properties in the hosted bean. But, won’t this complicate configuration? Will we have to maintain properties for use by Ant tasks and also properties needed by the managed objects?

Not necessarily; in Spring usage for example, the same property files used for Ant can also be used by Spring—even Ant’s placeholder syntax (${...}) is used. Spring has classes for this purpose, such as the PropertyPlaceHolderConfigurer. Hence, this approach will not introduce new configuration nightmares. See the sidebar “A Property of Properties” for more on this topic.

An alternative to pushing these properties by use of attributes is to call the target method with runtime arguments by using a call attribute or a nested methodCall element, the contents of which are Java expressions. The element is easier to use since XML-required noise, such as the entity escapes, can be avoided using CDATA:

 call="generateSite(&quot;${host.site.url}&quot;,&quot;${site.port}&quot;)" 

Or better:

 <methodCall><![CDATA[    generateSite("${host.site.url}","${site.port}")   ]]></methodCall> 

So the previous example could be written as:

  <target name="publish">
      <spring beanLocations="applicationContext.xml" beanName="siteGenerator">
         <methodCall>  generateSite("${host.site.url}","${site.port}")  </methodCall>
         Made a few tweaks.  Removed some sentence fragments.   
      </spring>                    
   </target> 

Of course, the target object must have the required method and argument signature.

The above examples simply introduce the SpringContextTask approach. Perhaps they can lead to alternatives or better implementations.

So one may question some of this Task extension’s features, such as the ability to call any method. This ability could even be removed, since any target bean that does not have an execute() method can be wrapped by one that does, a task even easier to complete within an IoC framework. But since supporting method expressions via OGNL (Object Graph Navigation Language, detailed later) is easy enough, method argument support is not an issue.

Interestingly, since any method can be called, the same object is reusable in the same build file to provide different services, thereby reducing excess Ant script clutter if each task invocation requires many attributes. This functionality would prove practical if the task instance could be referenced by an ID reference. Then we could write something like:

 

<spring id="metrics" beanLocations="metricsContext.xml" beanName="main" exampleAttribute="a value" and so forth . . ./>

<target name="ComputeMetrics"> <spring refid="metrics" call="computeNCSS"/> <spring refid="metrics" call="computeCCM"/> <spring refid="metrics" call="findBugs"/> </target>

<target name="genDocs"> <!- here are calls to other types of docs '/> <!- now call the metric docs '/> <spring refid="metrics" call="createDocs"/> </target>

Now we have a more readable format and more information hiding. We don’t care what is in the container, just that there is only one entry point, main. That bean could be an actual bean that does it all or, via dependency injection, delegate to other tools such as PMD, JavaNCSS, or FindBugs.

I chose not to develop the approach of reusing a SpringContextTask by a reference ID. Another way to accomplish this reuse is just to use different beans in the context, such as:

 <target name="ComputeMetrics">
   <spring beanLocations="metricsContext.xml" beanName="computeNCSS"/>
   <spring beanLocations="metricsContext.xml" beanName="computeCCM"/>
   <spring beanLocations="metricsContext.xml" beanName="findBugs"/>
</target> 

But each bean in this example must have an execute() method that kicks off the service. And, of course, each bean could actually just be referring to the same class or object.

Now that the requirements are known and use cases specified, let’s look at the prototype details.

Implementation

The implementation is simple and does not address many details, such as tapping into the Ant I/O subsystem or reusing the IoC container for multiple task invocations.

The UML diagram is shown below.

Figure 1. UML class diagram. Click on thumbnail to view full-sized image.

Listing 1 shows the abstract superclass of the Task extension. At runtime, Ant calls the execute() method. If a beanLocations attribute is specified in the task, an IoC application context is created (if not set) and the bean is accessed. Otherwise, if a class is specified, normal object instantiation is performed.

Next the Project, properties, and text are inserted into the object, depending on the presence of an appropriate setter method and whether the object is Ant-aware. So, in the non-IoC case, this task is a form of setter injection.

OGNL

When I implemented my task extension, the support for runtime arguments to the method call was problematic and, though not critical, a design challenge. One design called for adding an XML fragment to the task that would contain the arguments. Ant supports dynamic elements and a few examples of an XML fragment being used for this task are available on the Web. But this approach seemed overly complex and error prone.

Remembering prior research I did on OGNL, I decided to use it for this purpose. According to the OGNL Website, OGNL is “… Object-Graph Navigation Language; it is an expression language for getting and setting properties of Java objects. You use the same expression for both getting and setting the value of a property.” One great feature of OGNL is support for method calls, which I required.

OGNL supports a rich expression language. For example, the code below evaluates an indexed expression on the target object as part of the call:

  <methodCall>  <![CDATA[    notifyDeveloper(names(${dev}) ]]>       </methodCall>   

Another example of OGNL expression evaluation is found in one of the included unit tests, where a method-call expression used is: convertToString(employees[getNum()]). Though OGNL solved the problem I encountered and expressions offer a powerful new Ant capability, the use of attributes to define properties is the recommended approach and is compatible with the IoC pattern.

To show how the abstract class is used, the next section uses Spring to implement the actual Task extension.

Spring support

Listing 2 shows the SpringContextTask. This class is an example of how to target a specific IoC container. The Template Method pattern allowed this class to be simple; all the details of adapting the actual target object to support the requirements are in the parent class. Essentially, using any IoC container is a matter of how to find the container, get an object from it, and insert property values.

Listing 2. SpringContextTask class

 

/* * SpringContextTask.java * Created on Jan 9, 2005 * Creator: Josef Betancourt * Project: SpringContextTask * ----------------------------------------------------------------------------- * * Copyright 2005 by Josef Betancourt * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ----------------------------------------------------------------------------- * * */

package jbetancourt.ant.task.spring;

import java.util.Enumeration; import java.util.Properties;

import jbetancourt.ant.task.AbstractContextTask;

import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.PropertyOverrideConfigurer; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;

/** * * Ant task extension that invokes a POJO within a Spring Application * Context, a "springdef". * * Tested with Ant 1.6.2, JDK 1.4.2, Spring 1.1.4, and OGNL 2.6.7. * * This task is added to Ant with a taskdef or one of the new Ant 1.6+ * approaches. * * * * Example taskdef: * <taskdef name="springTask" * classname="jbetancourt.SpringContextTask" * classpathref="taskdefclasspath"> * * It is then used simply by accepting the defaults as: * <target name="test1"> * <springTask beanLocations="applicationContext.xml"> * </springTask></target> * * A more complex use is: * <target name="publishBean"> * <springTask * beanLocations="applicationContext.xml" * beanName="siteGenerator"

* host="${host.site.url}" * port="${site.port}" * methodName="generateSite"> * In-line site post change text * </springTask> * </target> * * * Use of methodCall element with CDATA expression. * * <target name="test8"> * <springTask beanLocations="applicationContext.xml" * beanName="antBean1"> * <methodCall><![CDATA[execute("Goodbye")]]> * </methodCall> * </springTask> * </target> * * * * * * @author JBETANCOURT * @since Jan 9, 2005 * */ public class SpringContextTask extends AbstractContextTask {

/** runtime Spring context that that will be set or created */ private ApplicationContext applicationContext;

/** * * Get the container managed bean. * * @param beanName * @return the pojo, singleton or non-singleton. */ public Object getBeanFromContainer(String beanName) throws BuildException { try { if(applicationContext == null){ applicationContext = new FileSystemXmlApplicationContext( getBeanLocations().list()); }

return applicationContext.getBean(getBeanName()); } catch (BeansException e) { throw new BuildException("Failure: could not get bean '" + getBeanName() + "' from context",e); } } /** * Invoke target bean setters with Ant specified dynamic properties. * */ public void insertManagedBeanProperties(){ // How to programmatically post process a Spring bean? // See Spring Forum thread for source of this approach. // http://forum.springframework.org/viewtopic.php?p=11833#11833 PropertyOverrideConfigurer poc = new PropertyOverrideConfigurer(); // The keys must be of form 'beanName.key'. // Create a new Properties with this format. Properties props = addKeyPrefix(getDynamicProperties(), getBeanName());

poc.setProperties(props);

((ConfigurableApplicationContext)applicationContext). addBeanFactoryPostProcessor(poc); ((ConfigurableApplicationContext)applicationContext).refresh(); } /** * For each key in props, convert to 'beanname.key' format. * * @return with keys modified * @throws BuildException */ private Properties addKeyPrefix(Properties initProps, String prefix) throws BuildException { // TODO: This seems like a wrong approach here. // Can the passed in props be manipulated instead?

Properties props = new Properties(); for (Enumeration en = initProps.propertyNames(); en.hasMoreElements();) { String key = (String) en.nextElement(); // Let Ant resolve any property replacements. String resolvedText = getProject().replaceProperties(initProps.getProperty(key)); props.put(prefix + "." + key, resolvedText); } return props; }

/** * Get the Spring context that was created or set. * @return could be null */ public ApplicationContext getApplicationContext() { return applicationContext; }

/** * * * @param applicationContext non-null * The applicationContext to set. */ public void setApplicationContext(ApplicationContext applicationContext) { log("setting applicationContext: " + applicationContext, Project.MSG_DEBUG); this.applicationContext = applicationContext; }

/* (non-Javadoc) * @see jbetancourt.ant.task.ExternalContainer#createContainer() */ public void createContainer() throws BuildException { try { if(applicationContext == null){ applicationContext = new FileSystemXmlApplicationContext( getBeanLocations().list()); } } catch (BeansException e) { throw new BuildException("Failure: could not create Spring container.",e); } } }

Note how the Spring implementation is very straightforward. Two of Ant’s features allowed this simplicity. First, any exceptions are wrapped by Ant’s unchecked BuildException, thereby simplifying further extensions. Second, Ant’s log system is being used, hiding logger implementation details.

The main complication is the method insertManagedBeanProperties(). Setting properties on an object is straightforward, however setting the properties within the container framework may be critical. The container semantics must be followed: the container may have or require the ability to act upon property-setting actions, such as event handling, auto-wiring, and transformations.

AOP implementation

Since most IoC frameworks support AOP, an alternative or supportive approach could leverage AOP on the IoC container side to allow even more flexibility. In Spring, for example, you could create a dedicated Ant factory bean and provide means to wire in the Ant properties and meet the other requirements. I did not pursue this approach for my task.

Testing

Ant provides support for unit testing with a JUnit Test subclass that makes Task testing more manageable. The source accompanying this article includes these tests. When run (using Maven or Ant), part of the output is:

 test:test:
    [junit] Running jbetancourt.ant.task.AbstractContextTaskTest
    [junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 1.752 sec
    [junit] Running jbetancourt.ant.task.spring.SpringContextTaskTest
    [junit] Tests run: 21, Failures: 0, Errors: 0, Time elapsed: 4.256 sec
BUILD SUCCESSFUL
Total time: 11 seconds 

The SpringContextTaskTest class has Ant-style task tests. In these tests, the JUnit subclass’s tests invoke targets in an Ant file using executeTarget() or similar methods:

  public void test2(){ 
      executeTarget("test2"); 
  }       
  public void test3(){
      expectBuildException("test3", "beanLocation or class must be specified");
  }    

The task tests are in the Ant file, SpringContextTaskTest.xml, which has the actual test targets, such as:

  <target name="test3">  <!-- No context path or class specified; should fail -->
     <springTask methodName="length"/></target> 

And, in our extension testing, this target references the Spring bean definitions in applicationContext.xml. These bean definitions consist mostly of one actual Java class being reused as different beans (Very low coupling!). Some examples:

  <bean id="antBean1" class="jbetancourt.TestBean1"/>     
   <bean id="beanWithProps" class="jbetancourt.TestBean1"/>
   <bean id="main" class="org.springframework.beans.factory.
     config.MethodInvokingFactoryBean">
      <property name="targetObject"><ref local="antBean"/></property>
      <property name="targetMethod"><value>execute<
        /value></property>
   </bean>                    

An interesting test is number 18, which uses a bean returned by an AOP proxy:

  <target name="test18">
      <springTask beanLocations="etc/contexts/applicationContext.xml" 
        beanName="postBugReport" methodName="doService"></springTask> 
    </target>        

And the bean definitions are:

 

<bean id="securityInterceptor" class="jbetancourt.SecurityInterceptor"> </bean> <bean id="postBugReport" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"><ref local="antBean"/></property> <property name="proxyInterfaces"> <list> <value>jbetancourt.ISecurity</value> </list> </property> <property name="interceptorNames"> <list> <value>securityInterceptor</value> </list>

</property> </bean>

The

SecurityInterceptor

could be a security advise for the build, such as a login filter (analogous to a servlet filter). The

postBugReport

targets the same

antBean

used before.

Deployment

Optimally, the IoC target beans and definition files or classes (or only classes if using programmatic IoC configurations) can be supplied in a deployment JAR. We can call these, when using Spring IoC, “springlets” (see Figure 2).

Using a hierarchy, externals can override defaults found in the deployment archive. Thus, the user is required to only edit an external property file and any child bean definition files to customize and provide default values. Of course, the externals can specify other collaborating IoC JARs.

Conclusions

In this article, I presented an Ant extension to allow the invocation of an IoC container-managed object, thereby increasing loose coupling. Alternatively, the same task can invoke any method on a nonmanaged object. By employing OGNL, method invocation using Java expressions was initiated, bypassing the need for more complex XML declarations. Optimization and leveraging advanced Ant and Spring features were not addressed.

Though presented as an Ant extension, the idea may be applicable to Maven and other build systems. Similarly, though the Spring framework was used, the approach is applicable to the use of other IoC frameworks, such as HiveMind, where the Ant task would have to specify a module deployment descriptor, or PicoContainer, even though it seems to favor programmatic configuration.

Josef Betancourt is a senior software engineer living in Rhode Island.