2
votes

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?

1

1 Answers

2
votes

Ok, so I figured it out myself (a new day, a fresh pair of eyes ;-).

It is actually largely as already written in my question:

 List<Marshaller<List<Airport>, RequestEntity>> marshallers = new ArrayList();
 marshallers.add(Jackson.marshaller());
 marshallers.add(xmlMarshaller());

 Marshaller<List<Airport>, RequestEntity> airportMarshaller = Marshaller.oneOf(JavaConversions.asScalaBuffer(marshallers).toSeq());

And in completeOK it is then effectively as such:

completeOK(airport, airportMarshaller);

The xmlMarshaller() and toXML(Object) methods are unchanged.

So, it turned out that I was on the right track but had simply lost track of the fact that I returned a List<Airport> instead of an Airport which was causing compiler errors.