Looking for a best way to deserialize STOMP messages payload to generic types.
I have websocket service which receives entity, saves it and sends message wrapping saved entity to all subscribed clients.
In plain JSON it should looks like that:
Client sends entity:
{
"name": "New Facility Name"
}
Message from service which I try to handle in client:
{
"type": "ADD",
"entity": {
"id": 123,
"name": "New Facility Name"
}
}
Here is Java realization which does not work as I expect.
Entity - JavaBean Facility
with fields id
and name
.
Message.
public final class UpdateMessage<T> {
private final Type type;
private final T entity;
@JsonCreator
public UpdateMessage(@JsonProperty("type") Type type, @JsonProperty("entity") T entity) {
this.type = type;
this.entity = entity;
}
// getters
public enum Type {
ADD,
DELETE,
EDIT
}
}
Test.
@RunWith(SpringRunner.class)
@SpringBootTest
public class WebsocketClientTest {
private CompletableFuture<UpdateMessage<Facility>> messageCompletableFuture = new CompletableFuture<>();
@Autowired
private WebsocketClient client;
@Before
public void setUp() {
client.connect();
}
@After
public void setDown() {
client.disconnect();
}
@Test
public void trySpringResolvableType() throws InterruptedException, TimeoutException, ExecutionException {
StompSession session = client.getSession();
session.subscribe("/topic/facility/update", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders stompHeaders) {
return ResolvableType.forClassWithGenerics(UpdateMessage.class, Facility.class).getType();
}
@Override
public void handleFrame(StompHeaders stompHeaders, Object payload) {
//noinspection unchecked
messageCompletableFuture.complete((UpdateMessage<Facility>) payload);
}
});
Facility facility = new Facility();
facility.setName("New Facility Name");
client.getSession().send("/app/facility/post", facility);
UpdateMessage<Facility> message = messageCompletableFuture.get(9, SECONDS);
/* Get 'java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Facility' here: */
assertEquals(facility.getName(), message.getEntity().getName());
}
}
Results with 'java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Facility'.
Tried also Jackson's TypeReference<T>
@Override
public Type getPayloadType(StompHeaders stompHeaders) {
return new TypeReference<UpdateMessage<Facility>>() {}.getType();
}
with same result and TypeFactory
@Override
public Type getPayloadType(StompHeaders stompHeaders) {
return TypeFactory.defaultInstance().constructParametricType(UpdateMessage.class, Facility.class);
}
which throws
org.springframework.messaging.converter.MessageConversionException: Unresolvable payload type [[simple type, class UpdateMessage<Facility>]] from handler type [class FacilityController$2]
from
org.springframework.messaging.simp.stomp.DefaultStompSession.invokeHandler(StompFrameHandler, Message<byte[]>, StompHeaders)
I've managed to get it work using wrapper class
public final class FacilityUpdateMessage {
private final UpdateMessage<Facility> message;
@JsonCreator
public FacilityUpdateMessage(@JsonProperty("type") UpdateMessage.Type type,
@JsonProperty("entity") Facility entity
) {
message = new UpdateMessage<>(type, entity);
}
public UpdateMessage<Facility> getMessage() {
return message;
}
}
@Override
public Type getPayloadType(StompHeaders stompHeaders) {
return FacilityUpdateMessage.class;
}
but I'm not sure if it is a best solution.