Search This Blog

Wednesday, June 27, 2007

Using Spring elements and attributes in your custom namespace

With Spring 2 it is possible to define your own namespace and have your own custom elements which you can use in the application context. Sometimes, allthough I do not promote this, you want to use Spring elements and attributes on your custom elements. For example: suppose I have a custom element named "foo:myCustomFooElement" with one attribute named "text". By default, you can't write something like this:

< foo:mycustomfooelement text="test" abstract="true">
< property name="property" value="value">
< /foo:myCustomFooElement>

This is because the property element and abstract attribute are not defined in your xsd which describes your custom element. Following is the source of a sample application I wrote to demonstrate the use of Spring properties and attributes on custom elements. The reason I do not promote this, is because you will loose the encapsulation of implementation details which your custom element hides. So use it with care.

public class Foo {
// This property gets injected by our custom FooParser
private String text;

// This property gets set by using Spring's < property> element
private Bar bar;

// getters and setters omitted

}

We will write a custom element for the Foo class.
public class Bar {
private int count;

// getter and setters omitted
}

Bar is just a regular class that gets injected in Foo. Below is the xsd I wrote to define myCustomFooElement:
< ?xml version="1.0" encoding="UTF-8"?>
< xsd:schema xmlns="http://www.mynamespace.com/foo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mynamespace.com/foo"
elementFormDefault="qualified" attributeFormDefault="unqualified">

< xsd:import
schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
namespace="http://www.springframework.org/schema/beans" />

< xsd:element name="myCustomFooElement">
< xsd:complextype>
< xsd:complexcontent>
< xsd:extension base="beans:identifiedType">
<!-- Here we re-use Spring elements and attributes -->
< xsd:group ref="beans:beanElements">
< xsd:attributegroup ref="beans:beanAttributes">
< xsd:attribute name="text" type="xsd:string"
use="required" />
< /xsd:extension>
< /xsd:complexContent>
< /xsd:complexType>
< /xsd:element>
< /xsd:schema>

The XML file containing the bean definitions looks like this:
< beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo="http://www.mynamespace.com/foo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.mynamespace.com/foo http://www.mynamespace.com/foo/foo.xsd">

< foo:mycustomfooelement id="foo" text="test" init="true">
<!-- Here we use a Spring property element inside our custom element -->
< property name="bar">
< bean class="nl.sample.customnamespace.Bar">
< property name="count" value="100">
< /bean>
< /property>
< /foo:myCustomFooElement>
< /beans>

To parse the myCustomFooElement we must create a namespacehandler and register a beandefinition parser like this:
public class FooNameSpaceHandler extends NamespaceHandlerSupport {
private static final String NAME_CUSTOM_FOO_ELEMENT = "myCustomFooElement";

public void init() {
registerBeanDefinitionParser(NAME_CUSTOM_FOO_ELEMENT, new FooParser());
}

class FooParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
// Here we parse the Spring elements such as < property>
BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement(element);
BeanDefinition bd = holder.getBeanDefinition();
bd.setBeanClassName(Foo.class.getName());

// Here we parse our custom attributes
String text = element.getAttribute("text");
bd.getPropertyValues().addPropertyValue("text", text);
parserContext.getRegistry().registerBeanDefinition(NAME_CUSTOM_FOO_ELEMENT, bd);
return (AbstractBeanDefinition) bd;
}
}
}

Don't forget to register the namespacehandler in a file called spring.handlers in the META-INF directory. The contents of de spring.handlers file looks like this:
http\://www.mynamespace.com/foo=nl.sample.customnamespace.FooNameSpaceHandler

Also, specify in the META-INF/spring.schemas file where Spring can find the foo.xsd:
http\://www.mynamespace.com/foo/foo.xsd=nl/sample/customnamespace/foo.xsd

Write a class to verify everything works:
public class Driver {
public static void main(String[] args) {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:nl/sample/customnamespace/customelements.xml");
Foo foo = (Foo) appContext.getBean("foo");
System.out.println("foo.text = " + foo.getText());
System.out.println("foo.bar.count = " + foo.getBar().getCount());
}
}

Extensive documentation about extensible XML authoring can be found in the reference manual. Eriks Wiersma has also written an excellent piece on his [URL="http://erik.jteam.nl/?p=23"]blog[/URL] about implementing custom namespaces.