I'm using HornetQ in embedded mode.
I'm trying to send from Publisher to Consumer a POJO object:
public class Pojo implements Serializable {
private Integer id;
private String name;
private String phone;
// constructors, getters & setters
}
My idea is to convert the POJO to a Map and send each properties via ClientMessage. (In this way, the Consumer will be able to filter messages by POJO's properties)
To achieve this, I'm using Jackson ObjectMapper.
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> pojoMap = mapper.convertValue(new Pojo(13, "name", "phone"), new TypeReference<Map<String, Object>>() {});
pojoMap.forEach(message::putObjectProperty);
producer.send(message);
consumer.setMessageHandler(message -> {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Pojo pojo = mapper.convertValue(mapJson, Pojo.class);
});
The problem is that during deserializing (in Consumer) ObjectMapper throws an exception:
java.lang.IllegalArgumentException: Cannot deserialize instance of 'java.lang.String' out of START_OBJECT token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: org.hornetq.core.example.Pojo["phone"])
...
From what I understood, ObjectMapper is looking for 'phone' value and wants a String, but it finds an Object and crashes.
How can I solve this problem?
Are there alternatives?
I also tried to use Gson instead of Jackson, but it returns me the same error.
The funny fact is that if you send an object which doesn't have any String parameters, it works without any problems.
(Shouldn't be necessary, but if you want) Here you can find the entire classes of:
This is not really obvious, but Jackson fails while deserializing org.hornetq.api.core.SimpleString
that Jackson is not aware of.
The same goes to Gson, since SimpleString
is not a standard class.
You have two options here:
SimpleString
serializer/deserializer.By the way, Message.toMap()
returns message system properties as well (your code in PasteBin seems to use it), so they can collide with your custom object properties (I don't use HornetQ so I can use wrong terms).
I believe, the properties should be normalized like this:
public static Map<String, Object> getNormalizedPropertiesFrom(final Message message) {
return message.getPropertyNames()
.stream()
.collect(Collectors.toMap(
SimpleString::toString,
simpleString -> {
final Object objectProperty = message.getObjectProperty(simpleString);
if ( objectProperty instanceof SimpleString ) {
return objectProperty.toString();
}
return objectProperty;
}
));
}
This, unlike Message.toMap()
, will discard system properties like durable
, address
, messageID
, expiration
, type
, priority
, and timestamp
.
So, what you need here is mapper.convertValue(getNormalizedPropertiesFrom(message), Pojo.class)
.
In this case, you can simplify properties extraction
public static Map<SimpleString, Object> getPropertiesFrom(final Message message) {
return message.getPropertyNames()
.stream()
.collect(Collectors.toMap(Function.identity(), message::getObjectProperty));
}
But your ObjectMapper
instance must be aware of how SimpleString
is (de)serialized.
Note that ObjectMapper.convertValue()
(I believe) uses intermediate objects while converting, it requires a serializer for this scenario (Message
-> Map<SimpleString, Object>
via custom serialization -> some intermediate representation via built-in deserialization -> Pojo
).
final ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new SimpleModule()
.addSerializer(SimpleString.class, new JsonSerializer<SimpleString>() {
@Override
public void serialize(@Nonnull final SimpleString simpleString, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeString(simpleString.toString());
}
})
)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
For performance reasons, you should use single ObjectMapper
instance.
Both options will produce:
Pojo{id=13, name=name, phone=phone}
for the Pojo
class,
{phone=phone, name=name, id=13}
for the properties, and something like
ClientMessage[messageID=8, durable=false, address=q49589558,userID=null,properties=TypedProperties[id=13,phone=phone,name=name]]
for the client message.