dimanche 29 mars 2015

How to consume a Jax-RS 2.0 Response from Mule Apikit



I am trying to consume a JAX-RS 2 Response with Jersey client 2.17.



Response response = ClientBuilder.newClient()
.target("http://localhost:8080/api")
.path("organisations")
.request(MediaType.APPLICATION_JSON)
.get(); //[1] .get(GetOrganisationsResponse.class);
//[2] System.out.println("headers=" + response.getHeaders());
//[3] String json = response.readEntity(String.class);
//[4] System.out.println("json=" + json);
Organisations orgs = response.readEntity(Organisations.class);


The response is served by Mule apikit.



<flow name="get:/organisations:my-api-config" >
<set-payload value="#[app.registry['organisations'].getOrganisations(message.inboundProperties['start'], message.inboundProperties['pages'])]" doc:name="Set Payload" />
</flow>


POJOs are generated from a RAML description. Here is the generated POJO for the above service:



/**
* A collection of organisations
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"size",
"organisations"
})
public class Organisations {

/**
*
* (Required)
*
*/
@JsonProperty("size")
private Integer size;
@JsonProperty("organisations")
private List<Organisation> organisations = new ArrayList<Organisation>();
@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();

/**
*
* (Required)
*
* @return
* The size
*/
@JsonProperty("size")
public Integer getSize() {
return size;
}

/**
*
* (Required)
*
* @param size
* The size
*/
@JsonProperty("size")
public void setSize(Integer size) {
this.size = size;
}

public Organisations withSize(Integer size) {
this.size = size;
return this;
}

/**
*
* @return
* The organisations
*/
@JsonProperty("organisations")
public List<Organisation> getOrganisations() {
return organisations;
}

/**
*
* @param organisations
* The organisations
*/
@JsonProperty("organisations")
public void setOrganisations(List<Organisation> organisations) {
this.organisations = organisations;
}

public Organisations withOrganisations(List<Organisation> organisations) {
this.organisations = organisations;
return this;
}

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}

@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}

public Organisations withAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
return this;
}
}


... and here is the generated GetOrganisationsResponse for the same service:



public class GetOrganisationsResponse
extends support.ResponseWrapper
{


private GetOrganisationsResponse(Response delegate) {
super(delegate);
}

/**
* OK
*
* @param entity
*
*/
public static Organisations.GetOrganisationsResponse withJsonOK(model.Organisations entity) {
Response.ResponseBuilder responseBuilder = Response.status(200).header("Content-Type", "application/json");
responseBuilder.entity(entity);
return new Organisations.GetOrganisationsResponse(responseBuilder.build());
}

}


In trying to GET an instance of Organisations on the client side, I observed the following:



  • All the properties from Organisations end up populated in the additionalProperties member suggesting none were recognized by the parser,

  • If I comment out the additionalProperties member and getter/setter, the parser's error is this:



com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "date" (class model.Organisations), not marked as ignorable (2 known properties: "size", "organisations"])




  • ... this makes sense if the date, headers (and others) fields were not mapped to the Response, but to the Organisations instead but why?

  • when uncommenting the lines marked 2, 3 and 4, the Response converted to String looks as follows. Shouldn't the GetOrganisationsResponse headers have been parsed and returned as a result of response.getHeaders() ?



headers={X-MULE_ENCODING=[UTF-8], Date=[Mon, 30 Mar 2015 02:16:57 +0000], Content-Length=[746], X-MULE_SESSION=[...], Connection=[close], http.status=[200], Content-Type=[application/json], Server=[Mule Core/3.6.1]} json={"date":null,"lastModified":null,"headers":{"Content-Type":["application/json"]},"entity":{"size":3,"organisations":[{"oid":"54df33936d725e370b000004","name":"org1","additionalProperties":{}},{"oid":"54df34406d725e370b000006","name":"org2","additionalProperties":{}},{"oid":"54df33c96d725e370b000005","name":"org3","additionalProperties":{}}],"additionalProperties":{}},"status":200,"mediaType":{"type":"application","subtype":"json","parameters":{},"wildcardType":false,"wildcardSubtype":false},"allowedMethods":[],"links":[],"cookies":{},"entityTag":null,"statusInfo":"OK","metadata":{"Content-Type":["application/json"]},"stringHeaders":{"Content-Type":["application/json"]},"length":-1,"language":null,"location":null}




  • Finally when trying to force the Response as generated on the server side with .get(GetOrganisationsResponse.class) (uncommenting 1), the outcome is:



Can not find a deserializer for non-concrete Map type [map type; class javax.ws.rs.core.MultivaluedMap, [simple type, class java.lang.String] -> [collection type; class java.util.List, contains [simple type, class java.lang.String]]]





  • The culprit is the following code in the parent class of GetOrganisationResponse called ResponseWrapper:


    @Override public MultivaluedMap getHeaders() { return delegate.getHeaders(); }


    @Override public MultivaluedMap getStringHeaders() { return delegate.getStringHeaders(); }




Thanks for pointing me in the right direction:



  1. Is client.get() or client.get(GetOrganisationsResponse.class) the recommended aproach to parse a Mule Apikit response on the client? (I could not find answers in Mule docs)

  2. Considering I get errors for either option: Thanks for your help&ideas on how to fix them.




Aucun commentaire:

Enregistrer un commentaire