Wednesday, 29 June 2011

Using the Spring 3 ConverterFactory Interface

Yesterday, I mentioned that the Guys at Spring have come up with a whole new way of doing type conversion that supersedes the property editor support mechanism. This is based on the org.springframework.core.convert package and also I demonstrated how to implement the Converter interface and wire it in with your Spring beans. I also said that it offers additional functionality to the property editor support method and this comes in the form of the ConverterFactory, GenericConverter, and ConditionalGenericConverter interfaces. The Spring documentation briefly details these extra interfaces and how to implement them.

This blog takes the idea of the ConverterFactory together with a contrived wine service scenario and adds to Spring's documentation to demonstrate how it can be used. In this contrived scenario, I have a WineService that has two setters, each requiring an Enum argument:

public class WineService {

 
private WineCountry wineCountry;

 
private WineColour wineColour;

 
@Required
 
public final void setWineCountry(WineCountry wineCountry) {

   
this.wineCountry = wineCountry;
 
}

 
@Required
 
public final void setWineColour(WineColour wineColour) {

   
this.wineColour = wineColour;
 
}

 
/** Some arbitrary business method that checks wine availability */
 
public boolean isWineAvailable(WineCountry wineCountry, WineColour wineColour) {

   
if (this.wineCountry == wineCountry && this.wineColour == wineColour) {
     
return true;
   
}

   
return false;
 
}
}

In order to convert these Enum arguments from strings specified in the XML config, we’ll need a couple of Converters. In this case, we can centralise the conversion logic from String to Enum as the conversion requirements for each Enum are similar and for this we can use a ConverterFactory.

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {

 
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {

   
return new StringToEnumConverter<T>(targetType);
 
}

 
@SuppressWarnings("rawtypes")
 
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

   
private final Class<T> enumType;

   
public StringToEnumConverter(Class<T> enumType) {

     
this.enumType = enumType;
   
}

   
@SuppressWarnings("unchecked")
   
public T convert(String source) {

     
checkArg(source);
     
return (T) Enum.valueOf(enumType, source.trim());
   
}

   
private void checkArg(String source) {

     
// In the spec, null input is not allowed
     
if (source == null) {
       
throw new IllegalArgumentException("null source is in allowed");
     
}
    }
  }
}

This code has been shamelessly plagiarised from the Spring documentation although I have added a couple of changes to get rid of the warnings in Eclipse together with a quick null check.

The XML config is also very similar to yesterday’s in that we’re specifying a business object and a ConversionServiceFactoryBean as a conversion service. It only differs in that the converters property of the ConversionServiceFactoryBean takes our ConverterFactory implementation: StringToEnumConverterFactory. That’s because the converters property can be any implementations of the Converter, ConverterFactory, GenericConverter, and ConditionalGenericConverter interfaces.

<?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">

 <bean id="wineService" class="miscillaneous.conversionservice.converterfactory.WineService">
  <property name="wineColour">
   <value>RED</value>
  </property>
  <property name="wineCountry">
   <value>FRANCE</value>
  </property>
 </bean>

 <bean id="conversionService"
      class="org.springframework.context.support.ConversionServiceFactoryBean">
     <property name="converters">
          <!-- Converters can be a list of converter or a ConverterFactory -->
        <bean class="miscillaneous.conversionservice.converterfactory.StringToEnumConverterFactory"/>
     </property>
 </bean>
</beans>

The code to test this is:

public class ConverterFactoryMain {

 
public static void main(String[] args) {

   
ApplicationContext ctx = new ClassPathXmlApplicationContext(
       
"ConverterFactoryService.xml");
    WineService wineService = ctx.getBean
(WineService.class);

    checkWine
(wineService, WineCountry.FRANCE, WineColour.RED);
    checkWine
(wineService, WineCountry.GERMANY, WineColour.WHITE);
 
}

 
private static void checkWine(WineService wineService, WineCountry wineCountry,
      WineColour wineColour
) {

   
System.out.println(wineCountry.toString()
       
+ " "
       
+ wineColour.toString()
       
+ " is "
       
+ (wineService.isWineAvailable(wineCountry, wineColour) == true ? "available"
           
: "unavailable"));
 
}
}

No comments: