Skip to content

Commit

Permalink
=doc #1290 Merge common/index and common/xml-support pages (#1386)
Browse files Browse the repository at this point in the history
* =doc #1290 Merge common/index and common/xml-support pages

Akka HTTP does not provide a Java API for handling XML so point to
Jackson for a custom integration.

* Add example on how to unmarshal XML using Jackson

* Make the Java XML support example more complete

* Fix typo: unmarshallar -> unmarshaller
  • Loading branch information
jonas authored and jrudolph committed Aug 31, 2017
1 parent 62ed17d commit 121f528
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 33 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ lazy val docs = project("docs")
case akka.Doc.BinVer(_) => ""
case _ => "cross CrossVersion.full"
}),
"jackson.version" -> Dependencies.jacksonVersion,
"extref.akka-docs.base_url" -> s"http://doc.akka.io/docs/akka/${Dependencies.akkaVersion.value}/%s",
"javadoc.akka.http.base_url" -> {
val v = if (isSnapshot.value) "current" else version.value
Expand Down
21 changes: 0 additions & 21 deletions docs/src/main/paradox/java/http/common/index.md

This file was deleted.

1 change: 1 addition & 0 deletions docs/src/main/paradox/java/http/common/index.md
1 change: 1 addition & 0 deletions docs/src/main/paradox/java/http/common/marshalling.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ The implicits for most of the predefined marshallers in Akka HTTP are provided t
`Marshaller` trait. This means that they are always available and never need to be explicitly imported.
Additionally, you can simply "override" them by bringing your own custom version into local scope.

<a id="custom-marshallers" />
## Custom Marshallers

Akka HTTP gives you a few convenience tools for constructing marshallers for your own types.
Expand Down
Empty file.
1 change: 1 addition & 0 deletions docs/src/main/paradox/java/http/common/xml-support.md
77 changes: 70 additions & 7 deletions docs/src/main/paradox/scala/http/common/xml-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,86 @@ Akka HTTP's @ref[marshalling](marshalling.md) and @ref[unmarshalling](unmarshall
infrastructure makes it rather easy to seamlessly support specific wire representations of your data objects, like JSON,
XML or even binary encodings.

For XML Akka HTTP currently provides support for [Scala XML](https://github.com/scala/scala-xml) right out of the box through it's
@@@ div { .group-java }

Akka HTTP does not currently provide a Java API for XML support. If you need to
produce and consume XML, you can write a @ref[custom marshaller](marshalling.md#custom-marshallers)
using [Jackson], which is also the library used for providing @ref[JSON support](json-support.md#json-jackson-support-java).

@@ snip [#jackson-xml-support] (../../../../../test/java/docs/http/javadsl/JacksonXmlSupport.java) { #jackson-xml-support }

The custom XML (un)marshalling code shown above requires that you depend on the `jackson-dataformat-xml` library.

sbt
: @@@vars
```
"com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % "$jackson.version$"
```
@@@

Gradle
: @@@vars
```
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '$jackson.version$'
```
@@@

Maven
: @@@vars
```xml
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>$jackson.version$</version>
</dependency>
```
@@@

@@@

@@@ div { .group-scala }

For XML Akka HTTP currently provides support for [Scala XML][scala-xml] right out of the box through it's
`akka-http-xml` module.

## Scala XML Support

The @scaladoc[ScalaXmlSupport](akka.http.scaladsl.marshallers.xml.ScalaXmlSupport) trait provides a `FromEntityUnmarshaller[NodeSeq]` and `ToEntityMarshaller[NodeSeq]` that
you can use directly or build upon.

In order to enable support for (un)marshalling from and to XML with [Scala XML](https://github.com/scala/scala-xml) `NodeSeq` you must add
In order to enable support for (un)marshalling from and to XML with [Scala XML][scala-xml] `NodeSeq` you must add
the following dependency:

@@@vars
```sbt
"com.typesafe.akka" %% "akka-http-xml" % "$project.version$"
```
@@@
sbt
: @@@vars
```
"com.typesafe.akka" %% "akka-http-xml" % "$project.version$" $crossString$
```
@@@

Gradle
: @@@vars
```
compile group: 'com.typesafe.akka', name: 'akka-http-xml_$scala.binary_version$', version: '$project.version$'
```
@@@

Maven
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-xml_$scala.binary_version$</artifactId>
<version>$project.version$</version>
</dependency>
```
@@@

Once you have done this (un)marshalling between XML and `NodeSeq` instances should work nicely and transparently,
by either using `import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._` or mixing in the
`akka.http.scaladsl.marshallers.xml.ScalaXmlSupport` trait.

@@@

[scala-xml]: https://github.com/scala/scala-xml
[jackson]: https://github.com/FasterXML/jackson
102 changes: 102 additions & 0 deletions docs/src/test/java/docs/http/javadsl/JacksonXmlExampleTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/

package docs.http.javadsl;

import org.junit.Test;
import static org.junit.Assert.assertEquals;

import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.annotation.JsonRootName;

import akka.http.javadsl.testkit.JUnitRouteTest;
import akka.http.javadsl.model.*;
import akka.http.javadsl.server.Route;
import akka.http.javadsl.unmarshalling.Unmarshaller;

public class JacksonXmlExampleTest extends JUnitRouteTest {

final String xml = "<point><x>3</x><y>4</y></point>";
final Point point = new Point() {
{
setX(3);
setY(4);
}
};

@Test
public void marshalXml() throws Exception {
final Route route = route(
completeOK(point, JacksonXmlSupport.<Point>marshaller())
);

testRoute(route)
.run(HttpRequest.GET("/"))
.assertStatusCode(StatusCodes.OK)
.assertEntity(xml);
}

@Test
public void unmarshalXml() throws Exception {
final Unmarshaller<HttpEntity, Point> unmarshaller = JacksonXmlSupport.unmarshaller(Point.class);

final Route route = route(
entity(unmarshaller, p -> {
assertEquals(p, point);
return complete(p.toString());
})
);

final RequestEntity entity = HttpEntities.create(ContentTypes.TEXT_XML_UTF8, xml);

testRoute(route)
.run(HttpRequest.POST("/").withEntity(entity))
.assertStatusCode(StatusCodes.OK)
.assertEntity("Point(x=3, y=4)");
}

@Test
public void unmarshalXmlDirect() throws Exception {
{
CompletionStage<Point> resultStage =
JacksonXmlSupport.unmarshaller(Point.class).unmarshal(
HttpEntities.create(ContentTypes.TEXT_XML_UTF8, xml),
system().dispatcher(),
materializer());

assertEquals(point, resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS));
}

{
CompletionStage<Point> resultStage =
JacksonXmlSupport.unmarshaller(Point.class).unmarshal(
HttpEntities.create(ContentTypes.create(MediaTypes.APPLICATION_XML, HttpCharsets.UTF_8), xml),
system().dispatcher(),
materializer());

assertEquals(point, resultStage.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
}

@JsonRootName("point")
public static class Point {
private int x, y;
public void setX(int x) { this.x = x; }
public int getX() { return this.x; }
public void setY(int y) { this.y = y; }
public int getY() { return this.y; }

public String toString() {
return "Point(x=" + x + ", y=" + y + ")";
}

public boolean equals(Object other) {
Point that = other instanceof Point ? (Point) other : null;
return that != null
&& that.x == this.x
&& that.y == this.y;
}
}
}
52 changes: 52 additions & 0 deletions docs/src/test/java/docs/http/javadsl/JacksonXmlSupport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/

package docs.http.javadsl;

//#jackson-xml-support
import java.io.IOException;
import java.util.List;
import java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import akka.http.javadsl.model.*;
import akka.http.javadsl.marshalling.Marshaller;
import akka.http.javadsl.unmarshalling.Unmarshaller;

public class JacksonXmlSupport {
private static final ObjectMapper DEFAULT_XML_MAPPER =
new XmlMapper().enable(SerializationFeature.WRAP_ROOT_VALUE);
private static final List<MediaType> XML_MEDIA_TYPES = Arrays.asList(MediaTypes.APPLICATION_XML, MediaTypes.TEXT_XML);

public static <T> Marshaller<T, RequestEntity> marshaller() {
return Marshaller.wrapEntity(
u -> toXML(DEFAULT_XML_MAPPER, u),
Marshaller.stringToEntity(),
MediaTypes.APPLICATION_XML
);
}

public static <T> Unmarshaller<HttpEntity, T> unmarshaller(Class<T> expectedType) {
return Unmarshaller.forMediaTypes(XML_MEDIA_TYPES, Unmarshaller.entityToString())
.thenApply(xml -> fromXML(DEFAULT_XML_MAPPER, xml, expectedType));
}

private static <T> String toXML(ObjectMapper mapper, T object) {
try {
return mapper.writeValueAsString(object);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot marshal to XML: " + object, e);
}
}

private static <T> T fromXML(ObjectMapper mapper, String xml, Class<T> expectedType) {
try {
return mapper.readerFor(expectedType).readValue(xml);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot unmarshal XML as " + expectedType.getSimpleName(), e);
}
}
}
//#jackson-xml-support
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ public int getFavoriteNumber() {
@Test
public void testEntity() {
//#example-entity-with-json
final Unmarshaller<HttpEntity, Person> unmarshallar = Jackson.unmarshaller(Person.class);
final Unmarshaller<HttpEntity, Person> unmarshaller = Jackson.unmarshaller(Person.class);

final Route route = entity(unmarshallar, person ->
final Route route = entity(unmarshaller, person ->
complete( "Person:" + person.getName() + " - favoriteNumber:" + person.getFavoriteNumber() )
);

Expand Down
8 changes: 5 additions & 3 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import scala.language.implicitConversions
object Dependencies {
import DependencyHelpers._

val jacksonVersion = "2.8.8"
val junitVersion = "4.12"
val h2specVersion = "1.5.0"
val h2specName = s"h2spec_${DependencyHelpers.osName}_amd64"
Expand Down Expand Up @@ -42,7 +43,7 @@ object Dependencies {
val sprayJson = "io.spray" %% "spray-json" % "1.3.3" // ApacheV2

// For akka-http-jackson support
val jackson = "com.fasterxml.jackson.core" % "jackson-databind" % "2.8.8" // ApacheV2
val jackson = "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion // ApacheV2

// For akka-http-testkit-java
val junit = "junit" % "junit" % junitVersion // Common Public License 1.0
Expand All @@ -53,7 +54,8 @@ object Dependencies {

object Docs {
val sprayJson = Compile.sprayJson % "test"
val gson = "com.google.code.gson" % "gson" % "2.3.1" % "test"
val gson = "com.google.code.gson" % "gson" % "2.3.1" % "test"
val jacksonXml = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % jacksonVersion % "test" // ApacheV2
}

object Test {
Expand Down Expand Up @@ -108,7 +110,7 @@ object Dependencies {

lazy val httpJackson = l ++= Seq(jackson)

lazy val docs = l ++= Seq(Docs.sprayJson, Docs.gson)
lazy val docs = l ++= Seq(Docs.sprayJson, Docs.gson, Docs.jacksonXml)
}


Expand Down

0 comments on commit 121f528

Please sign in to comment.