I'm using Akka HTTP (the Java version) to create REST API's. I have a working proof of concept that returns application/json. Unfortunately, I cannot find any clear documentation and/or (working) example of how to make it also return something else (in my case: text/xml).
This is the relevant bit of what I currently have (which returns a JSON-representation of the Airport object as long as I send a request with no Accept header or with Accept header application/json):
return route(
get(() ->
pathPrefix("reference", () ->
pathPrefix("v1", () ->
pathPrefix("airport", () ->
parameter("iataCode", (String iataCode) -> {
final CompletionStage<Optional<Airport>> futureMaybeAirport = fetchAirport(iataCode);
return onSuccess(
() -> futureMaybeAirport
, maybeAirport -> maybeAirport.map(airport ->
completeOK(airport, Jackson.marshaller())
).orElseGet(() ->
complete(StatusCodes.NOT_FOUND, "Not Found")
)
);
}
)
)
)
)
)
);
When I put text/xml in the Accept header, I get an HTTP 406. So, after some research it seems that I have to provide my own Marshaller to also be able to output text/xml. Knowing that Akka uses Jackson I'd like to use the jackson-dataformat-xml module which provides an XMLMapper. So, I create my custom Marshaller as follows (this is how Akka does it as well in their Jackson class):
public static <T> Marshaller<T, RequestEntity> xmlMarshaller() {
return Marshaller.wrapEntity(
u -> toXML(u),
Marshaller.stringToEntity(),
MediaTypes.TEXT_XML
);
}
private static String toXML(Object object) {
try {
return new XmlMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Cannot marshal to XML: " + object, e);
}
}
So far so good, but now the tricky part: how do I make it clear to Akka HTTP that I now want it to use either the default JSON Marshaller or this XML Marshaller? As far as I understand it you can do this by using Marshaller.oneOf. Unfortunately there is not a single practical example to be found of oneOf on the Net (and certainly not in Akka HTTP's Java documentation regarding Marshalling which is just a copy of the Scala-version with a note that the docs need to be fixed).
I think I need to do something like this:
List<Marshaller<Airport, RequestEntity>> marshallers = new ArrayList();
marshallers.add(Jackson.marshaller());
marshallers.add(xmlMarshaller());
Marshaller<Airport, RequestEntity> airportMarshaller = Marshaller.oneOf(JavaConversions.asScalaBuffer(marshallers).toSeq());
So that I can then adapt my completeOk to:
completeOK(airport, airportMarshaller)
Unfortunately I cannot get Marshaller.oneOf to work, the compiler keeps complaining:
incompatible types: inference variable A has incompatible equality constraints Object,Airport where A,B are type-variables:
A extends Object declared in method <A,B>oneOf(Seq<Marshaller<A,B>>)
B extends Object declared in method <A,B>oneOf(Seq<Marshaller<A,B>>)
It is unclear to me what exactly the problem is given that my A and B (Airport and RequestEntity) obviously extend Object (as does nearly everything in Java). So any help from a fresh pair of eyes would be appreciated!
Also: I'm assuming here that Akka HTTP's built-in content negotiation will determine which Marshaller to use based on the value in the Accept header. Am I correct in this assumption or am I trying to solve the problem in completely the wrong way?