I cannot get a custom Enum class property mapped from a String without some really weird configuration. The Enum class has a static .fromString method that I want used for any conversions but so far the recommended ways of doing this don't seem to work.. Here goes..
ServiceOperation class with a 'cmd' field. Command is a custom Enum class (the one I'm having issues with)
public class ServiceOperation
{
private String target;
private Command cmd;
...get and set for both fields...
}
ServiceOperationDTO class, which has all String fields.... mapping the .cmdStr of this class this back to ServiceOperation.cmd is the issue
public class ServiceOperationDTO
{
private String target;
private String cmdStr;
...get and set for both fields...
}
Command Enum class that has custom string mappings and static .fromString method
to convert back from strings
public enum Command
{
Up, Down, Once, Pause, Cont, Hup, Alarm, Interrupt, Quit, Usr1("1"), Usr2(
"2"), Term, Kill, Exit, Status, Start, Stop, Restart, Shutdown, ForceStop(
"force-stop"), ForceReload("force-reload"), ForceRestart(
"force-restart"), ForceShutdown("force-shutdown"), Check, Invalid;
private static final Map<String,Command> lookup = new HashMap<String,Command>();
static {
for(Command c : EnumSet.allOf(Command.class))
lookup.put(c.toString(), c);
}
Command()
{
this.value = this.name().toLowerCase();
}
Command(String svCmd)
{
this.value = svCmd;
}
@Override
public String toString()
{
return value;
}
public static Command fromString(String cmd) {
return lookup.get(cmd);
}
private final String value;
}
Going from ServiceOperation -> ServiceOpertationDTO works fine... However going from ServiceOperationDTO -> ServiceOperation fails except in one case. Here's the sample code that invokes the mapper and asserts whether the mapping worked.
ServiceOperationDTO dto = new ServiceOperationDTO();
dto.setCmd("force-stop");
dto.setTarget("myService");
ServiceOperation domainObj = mapper.map(dto, ServiceOperation.class);
System.out.format("DOMAIN target=%s cmd=%s\n", domainObj.getTarget(),
domainObj.getCmd());
assertTrue(domainObj.getCmd() == Command.ForceStop);
by default the .cmd field ends up as null since modelmapper doesn't know how to convert from String-> Command (which is expected).
The assertion fails since domainObj.cmd ends up being null. mapper.validate()
confirms with a validation exception saying...
Unmapped destination properties found in
TypeMap[ServiceOperationDTO -> ServiceOperation]:
ServiceOperation.setCmd(
Below are various ways I've tried to get things working (i.e. have modelmapper use Command.fromString to convert String->Command)..
- Tried adding an explicit Converter that goes from String->Command.. seemed easy enough from the docs. This doesn't work, the code doesn't get called at all. Stepping through the modelmapper code where it iterates through getMappings() showed there was no mapping for String->Command. However adding another Converter for String->String did work for all String fields. So I guess there's something about the Command class that makes modelmapper not add the mapping
mapper.addConverter(new Converter<String, Command>()
{
@Override
public Command convert(MappingContext<String, Command> context)
{
String source = context.getSource();
return source == null ? null : Command.fromString(source);
}
});
- Tried using createTypeMap, as shown in the example at #10
Same as before the code doesn't get called at all.
mapper.createTypeMap(String.class, Command.class).setConverter(
new Converter<String, Command>() {
public Command convert(MappingContext<String, Command> context)
{
String source = context.getSource();
return source == null ? null : Command.fromString(source);
}
});
- Tried adding a PropertyMap along with mapper.addMappings(), but still doesn't work
final Converter<String, Command> cmdToString = new AbstractConverter<String, Command>() {
protected Command convert(String source)
{
return source == null ? null : Command.fromString(source);
}
};
mapper.addMappings(new PropertyMap<ServiceOperationDTO, ServiceOperation>()
{
@Override
protected void configure()
{
using(cmdToString).map(source).setCmd(null);
}
});
...this code throws a MappingException....
Exception in thread "main" org.modelmapper.MappingException: ModelMapper mapping errors:
- Converter Main$1@7a763f5d failed to convert ServiceOperationDTO to Command.
1 error
at org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:338)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:77)
at org.modelmapper.ModelMapper.map(ModelMapper.java:185)
at Main.main(Main.java:97)
Caused by: java.lang.ClassCastException: ServiceOperationDTO cannot be cast to java.lang.String
at Main$1.convert(Main.java:1)
at org.modelmapper.AbstractConverter.convert(AbstractConverter.java:33)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:302)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:175)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:134)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:99)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:68)
... 2 more
- Tried using addMappings with a PropertyMap that just called the Command.fromString directly in the configure
mapper.addMappings(new PropertyMap<ServiceOperationDTO, ServiceOperation>()
{
@Override
protected void configure()
{
map().setCmd(Command.fromString(source.getCmd()));
}
});
this again doesn't end up getting called at all.
- By chance combined #1 (addConverter) and #4 (addMappings)
mapper.addMappings(new PropertyMap<ServiceOperationDTO, ServiceOperation>()
{
@Override
protected void configure()
{
map().setCmd(Command.fromString(source.getCmd()));
}
});
mapper.addConverter(new Converter<String, Command>()
{
@Override
public Command convert(MappingContext<String, Command> context)
{
String source = context.getSource();
return source == null ? null : Command.fromString(source);
}
});
this works, but both the code the configure
and the convert
get called, which is redundant. So the only way the converter gets called is if the map()
is done in the configure
. So it's kind of like a hacked way of getting the same behaviour you'd expected from #4. However I would have thought adding a generic converter from String->Command would handle all mappings of that type without requiring a specific PropertyMap<ServiceOperationDTO, ServiceOperation>
Seems to be a bug here if a String->String to converter works but not a String->Command. Are Enums being handled in a special way ? Is this a bug or do I need to mod my Command class to make it work ?