6

I had to upgrade several packages to pass a whitesource security scan and now that the dependencies are upgraded the custom HttpMessageConverter that was intercepting and building the response previously is no longer working. The relevant dependency upgrades are shown below.

Tomcat-embed-core 8.5.50 -> 9.0.30

Spring cloud contract release 2.0.1.RELEASE-> 2.0.6.RELEASE

Spring boot version 2.0.4.RELEASE -> 2.0.6.RELEASE

Jackson-databind 2.9.6 -> 2.10.0.pr1

Jackson-core: 2.10.1 -> 2.10.0.pr1

Here is the custom HttpMessageConverter that was working previously:

    private class JsonApiHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
    JsonApiHttpMessageConverter() {
        super(MediaType.valueOf(ResponseType.MEDIA_TYPE_JSON_API));
    }

    @Override
    protected boolean supports(final Class<?> clazz) {
        return clazz == HttpErrorResponse.class;
    }

    @Override
    protected Object readInternal(final Class<?> clazz, final HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(final Object o, final HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {
        try (OutputStreamWriter outputStream =
            new OutputStreamWriter(outputMessage.getBody(), Charset.defaultCharset())) {
            JsonAPIDocument document;
            if (o instanceof HttpErrorResponse) {
                // Build Error Document
                final HttpErrorResponse errorResponse = (HttpErrorResponse) o;
                final JsonApiErrorDTO errorDTO = new JsonApiErrorDTO(Integer.toString(errorResponse.getStatus()),
                    Integer.toString(errorResponse.getCode()), errorResponse.getMessage());
                document = new JsonAPIDocument();
                document.addError(errorDTO);
            } else { // Build JSON API Response Document
                final JsonApiDocumentBuilder documentBuilder = new JsonApiDocumentBuilder();
                documentBuilder.data(o);
                document = documentBuilder.build();
            }
            outputStream.write(new Gson().toJson(document));
            outputStream.flush();
        } catch (final InvalidJsonApiObjectException ijaoe) {
            LOG.error("Error in converting Object to JsonAPIDocument", ijaoe);
            throw new ServiceException(ijaoe);
        }
    }
}

it is defined in a class that registers the message converters like below

@Configuration
public class ServiceConfiguration implements WebMvcConfigurer {

...

    @Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
    converters.add(new JsonApiHttpMessageConverter());
    converters.add(new ByteArrayHttpMessageConverter());
    converters.add(new StringHttpMessageConverter());
    converters.add(new ResourceHttpMessageConverter());
    converters.add(new SourceHttpMessageConverter<>());
    converters.add(new AllEncompassingFormHttpMessageConverter());
    converters.add(new MappingJackson2HttpMessageConverter());
    converters.add(new MappingJackson2CborHttpMessageConverter());
    converters.add(new Jaxb2RootElementHttpMessageConverter());
}

@Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
    configurer.defaultContentType(MediaType.valueOf(ResponseType.MEDIA_TYPE_JSON_API));
}

}

this is the error response code:

@XmlRootElement(name = "error")
@XmlAccessorType(XmlAccessType.FIELD)
@JsonApiType(type = "error")
public class HttpErrorResponse implements Serializable {
    private static final long serialVersionUID = 1321088631120274988L;

    @XmlTransient
    private int status;

    @XmlElement(name = "code", required = true)
    private int code;

    @XmlElement(name = "message", required = true)
    @JsonApiAttribute(name = "detail")
    private String message;

    @JsonApiIgnore
    @XmlElement(name = "uuid", required = true)
    private String uuid;

    public HttpErrorResponse() {
        this.uuid = UUID.randomUUID().toString();
    }

    public HttpErrorResponse(final int status, final int code, final String message) {
        this();
        this.status = status;
        this.code = code;
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(final int status) {
        this.status = status;
    }

    public int getCode() {
        return code;
    }

    public void setCode(final int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(final String message) {
        this.message = message;
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(final String uuid) {
        this.uuid = uuid;
    }

}

and the object does appear to be the one being returned but the message converter is not converting it. The old response format with the converter working is:

"{\"included\":[],\"errors\":[{\"status\":\"400\",\"code\":\"990002\",\"detail\":\"Invalid subscription id: XYZ\"}]}",

and the current response is:

"responseBody": "{\"status\":400,\"code\":990002,\"message\":\"Invalid subscription id: XYZ\",\"uuid\":\"018fe1e3-3936-4c53-8612-61ef778fd811\"}",

I'm sorry if my question is unclear please let me know if there is anything I'm leaving out. I'm confused as to why the converter is no longer intercepting the response and updaing the format. Also here is the format that the converter should be building:

@JsonPropertyOrder({ "data", "included", "errors" })
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonAPIDocument extends AbstractJsonAPIDocument {

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private IJsonAPIDTO data;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public IJsonAPIDTO getData() {
        return data;
    }

    public void setData(IJsonAPIDTO data) {
        this.data = data;
    }
}

Here are the response headers:

"responseHeaders": {
    "X-Origin-Ref": "microservice",
    "X-Transaction-Ref": "b2e3d807-0d6f-40e7-90ad-43d5ecb455c2",
    "Content-Type": "application/vnd.api+json"
}

and ResponseType.MEDIA_TYPE_JSON_API is

public static final String MEDIA_TYPE_JSON_API = "application/vnd.api+json"

As a desperate attempt I changed the support method of the JsonApiHttpMessageConverter class to always return true and the converter is still not picking up the response

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
thurmc
  • 495
  • 1
  • 8
  • 29
  • is JsonApiHttpMessageConverter a top level class or inner/nested class? – Lionel Cichero Feb 03 '20 at 19:43
  • It is a nested class in the ServiceConfiguration class – thurmc Feb 03 '20 at 19:44
  • 1
    have you tried refactoring to a separate class? maybe the private modifier on the class is not allowing spring to load it as a bean. I know you are instantiating the object within the scope, but just in case might be worthy to try – Lionel Cichero Feb 03 '20 at 19:45
  • 1
    Gave it a shot but it still fails with the same issue :( – thurmc Feb 03 '20 at 21:39
  • have you checked that the configuration class is actually being recognized and it is initializing all the converters defined in it? – Lionel Cichero Feb 03 '20 at 22:38
  • It is executing the configureMessageConverters method and appears to be initializing everything they just aren't being executed – thurmc Feb 05 '20 at 23:31
  • Are you able to try updating just one of those libraries/environments at a time to try to further isolate the culprit? (I'd guess the bigger jumps: Tomcat, then Jackson 2.9 -> 2.10. That latter definitely had some non-trivial changes.) – dbreaux Feb 10 '20 at 15:22
  • Could you see if you can use appropriate spring boot starter instead of manually controlling versions ? – s7vr Feb 10 '20 at 21:08

0 Answers0