maks
maks

Reputation: 6006

Two-way converter in spring

Spring 3 has such a nice feature as type conversion. It provides a converter SPI(Converter<S, T>) to be used to implement differenet conversion logic. The subclass of Converter type allow to define one-way conversion(only from S to T), so if I want a conversion also to be performed from T to S I need to define another converter class that implement Converter<T, S>. If I have many classes which are subject to conversion, i need to define many converters. Is there any posibility to define two-way conversion logic(from S to T and from T to S) in one converter? and how it will be used?

PS. now I'm using my converters via ConversionServiceFactoryBean defining/injecting them in configuration file

Upvotes: 9

Views: 10348

Answers (3)

gshock
gshock

Reputation: 711

You can use Spring Formatter to format object of type T to String and vice versa.

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Using this interface you can achieve the same as the Barry Pitman says but with less code and this is the preferable way by the Spring documentation if you waht to format to String and vice versa. So the Barry's UserIdConverter class is going to look like this:

public class UserIdConverter implements Formatter<User> {

    private final UserDao userDao;

    @Autowired
    public UserIdConverter(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public User parse(String userId, Locale locale) {
        return userDao.load(userId);
    }

    @Override
    public String print(User target, Locale locale) {
        return target.getUserId();
    }
}

To register this Formatter you should include this in your XML config:

...
<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" >
    <property name="formatters">
        <set>
            <bean class="com.x.UserIdConverter"/>
        </set>
    </property>
</bean>
...

! NOTE that this class can be used only for formatting from some type T to String and vice versa. You can not format from type T to some other type T1 for example. If you have this case you should go with the Spring GenericConverter and use the Barry Pitman answer:

public abstract class AbstractTwoWayConverter<S, T> implements GenericConverter {

    private Class<S> classOfS;
    private Class<T> classOfT;

    protected AbstractTwoWayConverter() {
        Type typeA = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Type typeB = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        this.classOfS = (Class) typeA;
        this.classOfT = (Class) typeB;
    }

    public Set<ConvertiblePair> getConvertibleTypes() {
        Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
        convertiblePairs.add(new ConvertiblePair(classOfS, classOfT));
        convertiblePairs.add(new ConvertiblePair(classOfT, classOfS));
        return convertiblePairs;
    }

    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (classOfS.equals(sourceType.getType())) {
            return this.convert((S) source);
        } else {
            return this.convertBack((T) source);
        }
    }

    protected abstract T convert(S source);

    protected abstract S convertBack(T target);

}

/** 
 * converter to convert between a userId and user.
 * this class can be registered like so: 
 * conversionService.addConverter(new UserIdConverter (userDao));
 */ 
public class UserIdConverter extends AbstractTwoWayConverter<String, User> {

    private final UserDao userDao;

    @Autowired
    public UserIdConverter(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    protected User convert(String userId) {
        return userDao.load(userId);
    }

    @Override
    protected String convertBack(User target) {
        return target.getUserId();
    }
}

And add to your XML config:

...
<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" >
    <property name="converters">
        <set>
            <bean class="com.x.y.UserIdConverter"/>
        </set>
    </property>
</bean>
...

Upvotes: 1

Barry Pitman
Barry Pitman

Reputation: 3127

You are correct, if you want to use the org.springframework.core.convert.converter.Converter interface directly, you'll need to implement two converters, one for each direction.

But spring 3 has a couple of other options:

  1. If your conversion is not object-to-object but rather object-to-string (and back), then you can implement a org.springframework.format.Formatter instead. Formatters get registered as GenericConverters (see http://static.springsource.org/spring-webflow/docs/2.3.x/reference/html/ch05s07.html#converter-upgrade-to-spring-3)

  2. Otherwise you could implement your own org.springframework.core.convert.converter.GenericConverter, which makes it easy to create TwoWayConverter implementations using reflection.

    public abstract class AbstractTwoWayConverter<S, T> implements GenericConverter {
    
        private Class<S> classOfS;
        private Class<T> classOfT;
    
        protected AbstractTwoWayConverter() {
            Type typeA = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            Type typeB = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
            this.classOfS = (Class) typeA;
            this.classOfT = (Class) typeB;
        }
    
        public Set<ConvertiblePair> getConvertibleTypes() {
            Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
            convertiblePairs.add(new ConvertiblePair(classOfS, classOfT));
            convertiblePairs.add(new ConvertiblePair(classOfT, classOfS));
            return convertiblePairs;
        }
    
        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (classOfS.equals(sourceType.getType())) {
                return this.convert((S) source);
            } else {
                return this.convertBack((T) source);
            }
        }
    
        protected abstract T convert(S source);
    
        protected abstract S convertBack(T target);
    
    }
    
    /** 
     * converter to convert between a userId and user.
     * this class can be registered like so: 
     * conversionService.addConverter(new UserIdConverter (userDao));
     */ 
    public class UserIdConverter extends AbstractTwoWayConverter<String, User> {
    
        private final UserDao userDao;
    
        @Autowired
        public UserIdConverter(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        protected User convert(String userId) {
            return userDao.load(userId);
        }
    
        @Override
        protected String convertBack(User target) {
            return target.getUserId();
        }
    }
    

Upvotes: 20

Vantage
Vantage

Reputation: 41

Spring has just such an interface for this purpose: TwoWayConverter. see the following: http://static.springsource.org/spring-webflow/docs/2.0.x/javadoc-api/org/springframework/binding/convert/converters/TwoWayConverter.html

Upvotes: 4

Related Questions