Annotations

The annotations package provides for Java5 Annotations meta-data based definition of rules for Digester. This improves maintainability of both Java code and XML documents, as rules are now defined in POJOs and generating Digester parsers at run-time, avoiding manual updates.

Introduction

This is a brief overview of the digester-rules-in-Java5 Annotations feature. Inspired by the basic idea behind the JPA, BeanValidation and JAXB's specifications, this feature lets you define Digester rules directly in target POJOs, instead of creating and initializing the Rules objects programmatically, which can become tedious.

Annotation Rules

A digester rule on a POJO is expressed through one or more annotations. An annotation is considered a digester rule definition if its retention policy contains RUNTIME and if the annotation itself is annotated with org.apache.commons.digester3.annotations.DigesterRule.

The DigesterRule is defined by the combination of:

  • the reflected Class<? extends org.apache.commons.digester3.Rule> by the annotation;
  • the org.apache.commons.digester3.annotations.AnnotationHandler class that has to be invoked during the target class traversal.

Digester annotations can target any of the following ElementTypes:

  • FIELD for Digester rules concerning attributes;
  • METHOD for Digester rules concerning methods calls;
  • PARAMETER for Digester rules concerning methods parameters setting;
  • TYPE for Digester rules concerning types creation.

While other ElementTypes are not forbidden, the Digester annotations processor does not have to recognize and process annotation rules placed on such types.

Every Digester rule annotation must define a pattern element of type String that represents the element matching path pattern, and the namespaceURI for which the Rule is relevant.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@CreationRule
@DigesterRule(
    reflectsRule = ObjectCreateRule.class,
    providedBy = ObjectCreateRuleProvider.class
)
public @interface ObjectCreate
{

    String pattern();

    String namespaceURI() default "";

}

Applying multiple annotation rule of the same type

It is often useful to declare the same annotation rule more than once to the same target, with different properties. To support this requirement, the Digester annotation processor treats annotations annotated by @org.apache.commons.digester3.annotations.DigesterRuleList whose value element has a return type of an array of rule annotations in a special way. Each element in the value array are processed by the Digester annotation processor as regular annotation rule annotations. This means that each Digester rule specified in the value element is applied to the target. The annotation must have retention RUNTIME and can be applied on a type, field, method or method parameter. It is recommended to use the same set of targets as the initial Digester annotation rule.

Note to designers: each Digester annotation rule should be coupled with its corresponding multi-valued annotation. It is recommended, though not mandated, the definition of an inner annotation named List.

@Documented
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
@CreationRule
@DigesterRule(
    reflectsRule = ObjectCreateRule.class,
    handledBy = ObjectCreateHandler.class
)
public @interface ObjectCreate
{

    String pattern();

    String namespaceURI() default "";

    @Documented
    @Retention( RetentionPolicy.RUNTIME )
    @Target( ElementType.TYPE )
    @DigesterRuleList
    @interface List
    {
        ObjectCreate[] value();
    }

}

AnnotationHandler implementation

An AnnotationHandler implementation performs the rule binding of a given annotation for a given annotated element. The implementation classes are specified by the handledBy element of the @DigesterRule annotation that decorates the rule annotation definition. The rule provider implementation implements the org.apache.commons.digester3.annotations.AnnotationHandler<A extends Annotation, E extends AnnotatedElement> interface.

class ObjectCreateHandler
    implements AnnotationHandler<ObjectCreate, Class<?>>
{

    public void handle( ObjectCreate annotation, Class<?> element, RulesBinder rulesBinder )
    {
        rulesBinder.forPattern( annotation.pattern() )
            .withNamespaceURI( annotation.namespaceURI() )
            .createObject()
                .ofType( element )
                .ofTypeSpecifiedByAttribute( annotation.attributeName() != null ? annotation.attributeName() : null );
    }

}
Notes

A new instance of the AnnotationHandler will be created each time the Digester annotations processor will meet the relative rule that requests it.

To supply the missing AnnotatedElement for methods PARAMETERs, the Digester annotation processor come with the org.apache.commons.digester.annotations.reflect.MethodArgument class.

Built-in Rules

All built-in annotation rules are in the package org.apache.commons.digester3.annotations.rules. Here is the list of annotations and their usage.

TYPE annotations

AnnotationReflect rule
@ObjectCreateorg.apache.commons.digester3.ObjectCreateRule
@FactoryCreateorg.apache.commons.digester3.FactoryCreateRule

FIELD annotations

AnnotationReflect rule
@BeanPropertySetterorg.apache.commons.digester3.BeanPropertySetterRule
@SetPropertyorg.apache.commons.digester3.SetPropertiesRule

METHOD annotations

AnnotationReflect rule
@CallMethodorg.apache.commons.digester3.CallMethodRule
@SetNextorg.apache.commons.digester3.SetNextRule
@SetRootorg.apache.commons.digester3.SetRootRule
@SetToporg.apache.commons.digester3.SetTopRule

PARAMETER annotations

AnnotationReflect rule
@CallParamorg.apache.commons.digester3.rule.CallParamRule

Bootstrapping

The core of Digester annotations rules processor is the org.apache.commons.digester3.annotations.FromAnnotationsRuleModule class, a specialization of org.apache.commons.digester3.RulesModule.

A FromAnnotationsRuleModule instance is able to analyze Class<?> graphs and builds the relative rule bindings to create org.apache.commons.digester3.Digester instances.

An org.apache.commons.digester3.annotations.AnnotationHandlerFactory implementation performs the creation of org.apache.commons.digester3.annotations.AnnotationHandler<A extends Annotation, E extends AnnotatedElement> instances; the default implementation is limited to create the handler by invoking the default empty constructor of the required class, but users are free to give their implementation if they need a more complex factory, i.e. providers requires components that could be injected from a context, etc. etc.

Said that, to obtain a fresh new org.apache.commons.digester3.binder.DigesterLoader instance with default factories, it is enough extending the org.apache.commons.digester3.annotations.FromAnnotationsRuleModule class:

class MyModule
    extends FromAnnotationsRuleModule
{

    @Override
    protected void configureRules()
    {
        bindRulesFrom( MyType1.class );
        bindRulesFrom( MyType2.class );
        bindRulesFrom( MyType3.class );
        ...
    }

}

Otherwise, if users need specify their custom factory:

class MyModule
    extends FromAnnotationsRuleModule
{

    @Override
    protected void configureRules()
    {
        useAnnotationHandlerFactory( new MyAnnotationHandlerFactory() );
        ...
        bindRulesFrom( MyType1.class );
        bindRulesFrom( MyType2.class );
        bindRulesFrom( MyType3.class );
        ...
    }

}

Example: a simple RSS parser

Let's assume there is the need to parse the following (simplified) XML/RSS feed:

<rss version="2.0">
  <channel>

    <title>Apache</title>
    <link>http://www.apache.org</link>
    <description>The Apache Software Foundation</description>
    <language>en-US</language>
    <rating>(PICS-1.1 "http://www.rsac.org/ratingsv01.html"
      2 gen true comment "RSACi North America Server"
      for "http://www.rsac.org" on "1996.04.16T08:15-0500"
      r (n 0 s 0 v 0 l 0))</rating>

    <image>
      <title>Apache</title>
      <url>http://jakarta.apache.org/images/jakarta-logo.gif</url>
      <link>http://jakarta.apache.org</link>
      <width>505</width>
      <height>480</height>
      <description>The Jakarta project. Open source, serverside java.</description>
    </image>

    <item>
      <title>Commons Attributes 2.1 Released</title>
      <link>http://jakarta.apache.org/site/news/news-2004-2ndHalf.html#20040815.1</link>
      <description>The Apache Commons team is happy to announce the release of Commons Attributes 2.1.
      This is the first release of the new Commons-Attributes code.</description>
    </item>

    <item>
      <title>Cloudscape Becomes Apache Derby</title>
      <link>http://jakarta.apache.org/site/news/elsewhere-2004-2ndHalf.html#20040803.1</link>
      <description>IBM has submitted a proposal to the Apache DB project for a Java-based package to be called 'Derby'.</description>
    </item>

    <item>
      <title>Commons BeanUtils 1.7 Released</title>
      <link>http://jakarta.apache.org/site/news/news-2004-2ndHalf.html#20040802.1</link>
      <description/>
    </item>

    <item>
      <title>Commons JXPath 1.2 Released</title>
      <link>http://jakarta.apache.org/site/news/news-2004-2ndHalf.html#20040801.2</link>
      <description/>
    </item>
  </channel>
</rss>

So, let's define the Java entities and annotate them; first the Channel entity:

@ObjectCreate( pattern = "rss/channel" )
class Channel
{

    private final List<Item> items = new ArrayList<Item>();

    @BeanPropertySetter( pattern = "rss/channel/title" )
    private String title;

    @BeanPropertySetter( pattern = "rss/channel/link" )
    private String link;

    @BeanPropertySetter( pattern = "rss/channel/description" )
    private String description;

    @BeanPropertySetter( pattern = "rss/channel/language" )
    private String language;

    private Image image;

    // getters and setters

    @SetNext
    public void setImage( Image image )
    {
        this.image = image;
    }

    @SetNext
    public void addItem( Item item )
    {
        this.items.add( item );
    }

}

Then the Image entity:

@ObjectCreate( pattern = "rss/channel/image" )
class Image
{

    @BeanPropertySetter( pattern = "rss/channel/image/description" )
    private String description;

    @BeanPropertySetter( pattern = "rss/channel/image/width" )
    private int width;

    @BeanPropertySetter( pattern = "rss/channel/image/height" )
    private int height;

    @BeanPropertySetter( pattern = "rss/channel/image/link" )
    private String link;

    @BeanPropertySetter( pattern = "rss/channel/image/title" )
    private String title;

    @BeanPropertySetter( pattern = "rss/channel/image/url" )
    private String url;

    // getters and setters

}

and finally the Item entity:

@ObjectCreate( pattern = "rss/channel/item" )
class Item
{

    @BeanPropertySetter( pattern = "rss/channel/item/description" )
    private String description;

    @BeanPropertySetter( pattern = "rss/channel/item/link" )
    private String link;

    @BeanPropertySetter( pattern = "rss/channel/item/title" )
    private String title;

    // getters and setters

}

It is now possible to create the Digester instance and parse the XML:

import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
...
DigesterLoader loader = newLoader( new FromAnnotationsRuleModule()
    {

        @Override
        protected void configureRules()
        {
            bindRulesFrom( Channel.class );
        }

    } );
...
Digester digester = digesterLoader.newDigester();
try
{
    Channel channel = digester.parse( new URL( "http://www.myfeedprovider.com/rss.xml" ).openStream() );
}
catch (Exception e)
{
    // do something
}