Monday, 27 June 2011

Writing a Custom Property Editor in Spring

Spring’s XML configuration file specifies the beans you wire together to create your application's context. When building your application context Spring, behind the scenes, is busy converting the property values in your XML file, written as Strings, into the correct type required by your bean and it does this using a set of built in property editors. A property editor is an Adaptor class that converts a String value in your XML file into the type required by your bean. For example, given the following bean setter:

  private int myValue;

 
public void setMyValue(int value) {

   
myValue = value;
 
}

...then the following XML can be used to set the value to 10:

<bean id="myValueBean" class="package.MyValueBean">
  <property name="myValue">
   <value>10</value>
  </property>
 </bean>

In this case the value 10 in the XML file is converted to an integer and passed to your bean by Spring.

A question now arises: what happens when Spring can’t cope as it doesn’t have a built in property editor? In this case, you need to write your own custom property editor and wire it in to the application context through your XML file.

Like all good pieces of code explanation, we need some far fetched, highly unlikely scenario to serve as an example. In this case I have an object that takes a domain name and checks whether or not it’s available. To make thing slightly more difficult, this object will only test either .com or .co.uk top level domain names. To enforce this rule we need to supply the object with a regular expression against which we can validate the format of a domain name argument. In our XML file, we specify the regular express as a string, but its associated setter method requires a java.util.regex.Pattern; hence, we need to convert the regex string into a Pattern.

The first step here is to jot down the business object code:

public class DomainName {

 
private Pattern pattern;

 
@Required
 
public void setPattern(Pattern pattern) {

   
this.pattern = pattern;
 
}

 
public boolean isAvailable(String domainName) {

   
checkDomainNameArgument(domainName);
   
return remoteDomainAvailable(domainName);
 
}

 
private void checkDomainNameArgument(String host) {

   
Matcher matcher = pattern.matcher(host);

   
if (!matcher.matches()) {
     
throw new IllegalArgumentException("Invalid address format: " + host);
   
}
  }

 
@SuppressWarnings("unused")
 
private boolean remoteDomainAvailable(String host) {

   
// TODO try to connect to remote domain
    // TODO return true if successfully connects else return false
   
return true;
 
}
}

As you can see, this is a simple non-functioning stub with a setter method requiring a java.util.regex.Pattern. The business method isAvailable(...) uses the pattern to check the domain name argument, throwing an IllegalArgumentException if it does not match the regular expression.

The next thing to do is to create an appropriate property editor support class to convert the domain name string in to a Pattern object:

public class RegexPropertyEditor extends PropertyEditorSupport {

 
public void setAsText(String regex) {
   
Pattern pattern = Pattern.compile(regex);
    setValue
(pattern);
 
}
}

Having written the property editor, we now need to tell Spring about it. The XML file below both registers the DomainName business object, and the custom property support class: org.springframework.beans.factory.config.CustomEditorConfigurer.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <!-- Declare our bean -->
 <bean id="domainNameTest" class="miscillaneous.propertyeditorsupport.DomainName">
  <property name="pattern">
   <value>[a-zA-Z0-9\.]+(com|co.uk)</value>
  </property>
 </bean>

 <!-- The pattern property above won't take a string so we -->
 <!-- need to declare one of these -->
 <bean name="customEditorConfigurer" 
     class="org.springframework.beans.factory.config.CustomEditorConfigurer">
     <property name="customEditors">
         <map>
             <entry key="java.util.regex.Pattern">
                 <bean class="miscillaneous.propertyeditorsupport.RegexPropertyEditor" />
             </entry>
             <!-- TODO Add other entries here when required -->
         </map>
     </property>
 </bean>
</beans>

Finally, the code below will demonstrate that this all works:

public class PropertyEditorSupportMain {

 
public static void main(String[] args) {

   
ApplicationContext ctx = new ClassPathXmlApplicationContext(
       
"PropertyEditorSupport.xml");

    DomainName ipAddress = ctx.getBean
("domainNameTest", DomainName.class);
   
// This will work
   
testDomainName(ipAddress, "www.captaindebug.com");
   
// This will throw an IllegalArgumentException
   
testDomainName(ipAddress, "www.captaindebug.fr");
 
}

 
private static void testDomainName(DomainName ipAddress, String name) {

   
System.out.println("This IP address is "
       
+ (ipAddress.isAvailable(name) == true ? "available" : "unavailable"));
 
}
}

This blog has explained how to write Spring 2 property editors, which is all well and good, but the Guys at Spring, under the banner of simplification, have written a new property conversion mechanism that fits more snugly in with the use of annotations. This primarily seems to be have been developed in compliance with JSR 303: bean validation for the new Spring 3 MVC stuff. So, my next (or some future) blog will, for comparison, implement the regex Pattern property editor using the new JSR 303 annotation mechanism... just as soon as I get around to it... possibly.

No comments: