Taras
Taras

Reputation: 106

How to use an interface type instead of Object? Converting problem

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

Answers (1)

Taras
Taras

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

Related Questions