Reputation: 106
I want to use Event interface type for eventData field in EventDto class. I also have impl of Event its PrintEvent.
Before that I used Object instead of Event for eventData field and everything worked fine. But when I choose to use Event instead of Object for eventData it’s start throwing an exception.
I don’t want to use Object because it’s unclear for code readability.
Error:
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.app.dto.Event]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.app.dto.Event
(no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 5, column: 26] (through reference chain: com.app.dto.SubmitEventsRequest["events"]->java.util.ArrayList[0]->com.app.dto.EventDto["eventData"])
public interface Event {
}
@Data
@Builder
public class EventDto {
private IncomingEventsTypeEnum eventType;
private Event eventData;
}
@Data
public class PrintEvent implements Event {
private Long id;
private String printerIp;
private String port;
private PrinterTaskStatusEnum status;
private String zpl;
private String statusMessage;
}
@Data
@NoArgsConstructor
public class SubmitEventsRequest {
@NotNull
private List<EventDto> events;
}
@PostMapping("/events")
public ResponseEntity<ResponseStatus> submitEvents(@RequestParam String token,
@RequestBody SubmitEventsRequest request) {
return eventsService.submitEvents(token, request);
}
@Component
public class CommunicationAwareServiceReceiveRegistryImpl implements CommunicationAwareServiceReceiveRegistry {
private final Map<IncomingEventsTypeEnum, EventHandler> eventHandlers = new ConcurrentHashMap<>();
@Override
public void submit(IncomingEventsTypeEnum eventType, Event eventData) {
EventHandler eventHandler = eventHandlers.get(eventType);
if (eventHandler != null) {
eventHandler.handle(eventData);
} else {
throw new IllegalArgumentException("Unknown event type: " + eventType);
}
}
@Override
public void registerEventHandler(IncomingEventsTypeEnum eventType, EventHandler eventHandler) {
eventHandlers.put(eventType, eventHandler);
}
}
public interface CommunicationAwareServiceReceiveRegistry {
void submit(IncomingEventsTypeEnum eventType, Event eventData);
void registerEventHandler(IncomingEventsTypeEnum eventType, EventHandler eventHandler);
}
@Component
@AllArgsConstructor
public class PrintSubmittedEventHandler implements EventHandler {
private final PrinterService printerService;
private final CommunicationAwareServiceReceiveRegistry communicationReceiveRegistry;
private final ObjectMapper objectMapper;
@PostConstruct
public void init() {
communicationReceiveRegistry.registerEventHandler(IncomingEventsTypeEnum.PRINT_SUBMITTED, this);
}
@Override
public void handle(Event eventData) {
PrintEvent pojo = objectMapper.convertValue(eventData, PrintEvent.class);
Long id = pojo.getId();
String statusMessage = pojo.getStatusMessage();
printerService.updateStatus(id, PrinterTaskStatusEnum.SUBMITTED, statusMessage);
}
}
@Component
@AllArgsConstructor
public class PrintFailedEventHandler implements EventHandler {
private final PrinterService printerService;
private final CommunicationAwareServiceReceiveRegistry communicationReceiveRegistry;
private final ObjectMapper objectMapper;
@PostConstruct
public void init() {
communicationReceiveRegistry.registerEventHandler(IncomingEventsTypeEnum.PRINT_FAILED, this);
}
@Override
public void handle(Event eventData) {
PrintEvent pojo = objectMapper.convertValue(eventData, PrintEvent.class);
Long id = pojo.getId();
String statusMessage = pojo.getStatusMessage();
printerService.updateStatus(id, PrinterTaskStatusEnum.FAILED, statusMessage);
}
}
curl --location --request POST 'http://localhost:8080/api/events?token=502423b8990942c7a3f470a382d25658' \
--header 'Content-Type: application/json' \
--data-raw '{
"events": [
{
"eventType": "PRINT_SUBMITTED",
"eventData": {
"id": 7,
"statusMessage" : "OK"
}
}
]
}'
Upvotes: 0
Views: 241
Reputation: 106
I don’t think that it’s a good solution. But it looks like it’s working.
I added @JsonDeserialize(using = EventDeserializer.class)
to EventDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonDeserialize(using = EventDeserializer.class)
public class EventDto {
private IncomingEventsTypeEnum eventType;
private Event eventData;
}
And created EventDeserializer.class
@Slf4j
public class EventDeserializer extends JsonDeserializer<EventDto> {
@Override
public EventDto deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
JsonNode node = p.getCodec().readTree(p);
IncomingEventsTypeEnum eventType = IncomingEventsTypeEnum.valueOf(node.get("eventType").asText());
Event eventData = switch (eventType) {
case PRINT_FAILED, PRINT_SUBMITTED -> p.getCodec().treeToValue(node.get("eventData"), PrintEvent.class);
};
return EventDto.builder()
.eventType(eventType)
.eventData(eventData)
.build();
}
}
Then removed objectMapper from handle methods and just cast to my type.
@Override
public void handle(Event eventData) {
PrintEvent pojo = (PrintEvent) eventData;
Long id = pojo.getId();
String statusMessage = pojo.getStatusMessage();
printerService.updateStatus(id, PrinterTaskStatusEnum.SUBMITTED, statusMessage);
}
@Override
public void handle(Event eventData) {
PrintEvent pojo = (PrintEvent) eventData;
Long id = pojo.getId();
String statusMessage = pojo.getStatusMessage();
printerService.updateStatus(id, PrinterTaskStatusEnum.FAILED, statusMessage);
}
If anyone has a better solution, I'd love to hear it.
Upvotes: 0