David Parks David Parks - 1 month ago 6
Java Question

Spring custom converter for all Enums

I've built many Enum classes with

int getID()
and
MyEnum withID(int)
methods that allow me to dedicate an ID to the enum values for persistence purposes (thus avoiding changes due to order/name change with regards to external storage of the enum).

I'd like to build a custom converter that does some reflection to look for these methods, and use them or back up to Ordinal/String conversions when they are not found.

Does a generic Enum converter seem possible to anyone? This is only my second foray into Converters.

Answer

I'd say you are trying to solve the wrong problem. I usually persist enums as Strings and thereby avoid this problem in the first place. The drawback is of course a larger DB field, but that's hardly significant.


That said:

I'd say it's possible in general, but not in a clean way. What I'd do is let all these enums implement a common interface (either just a marker interface or one that contains the int getId() method). Now register your PropertyEditor for this interface only, then you're not breaking too much standard functionality.

Then, your next problem is that you are relying on static factory methods, which can not be done in a generic way. Of course your PropertyEditor can do:

enumClass.getDeclaredMethod("withId", int.class).invoke(id)

but I'd call that very hacky. How about something like this:

Object target = null;
for(Object e : EnumSet.allOf(yourEnumClass)){
    if(e instanceof MyInterface && ((MyInterface)e).getId()==thisId){
        target = e;
        break;
    }
}
return target;

Now you are not using any static factory methods and you have compile time safety, as long as your enums implement a common interface.


Update: with the new Converter SPI it gets easier. Use a custom ConverterFactory:

public class CustomEnumConverterFactory implements
    ConverterFactory<String, Enum<?>>{

    @Override
    public <T extends Enum<?>> Converter<String, T> getConverter(
        final Class<T> targetType){
        return WithId.class.isAssignableFrom(targetType)
            ? new EnumWithIdConverter(targetType)
            : new StandardEnumConverter(targetType);
    }

}

Register it like this:

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <!-- converters is a set of both converters and converterfactories -->
        <bean class="foo.bar.CustomEnumConverterFactory" />
    </property>
</bean>