2

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.

mittah
  • 41
  • 6

0 Answers0