diff --git a/build.sbt b/build.sbt
index 8d6967221b4..0dbcc575944 100644
--- a/build.sbt
+++ b/build.sbt
@@ -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
diff --git a/docs/src/main/paradox/java/http/common/index.md b/docs/src/main/paradox/java/http/common/index.md
deleted file mode 100644
index fc582418710..00000000000
--- a/docs/src/main/paradox/java/http/common/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Common Abstractions (Client- and Server-Side)
-
-HTTP and related specifications define a great number of concepts and functionality that is not specific to either
-HTTP's client- or server-side since they are meaningful on both end of an HTTP connection.
-The documentation for their counterparts in Akka HTTP lives in this section rather than in the ones for the
-@ref[Client-Side API](../client-side/index.md), @ref[Low-Level Server-Side API](../server-side/low-level-api.md) or @ref[High-level Server-Side API](../routing-dsl/index.md),
-which are specific to one side only.
-
-@@toc { depth=3 }
-
-@@@ index
-
-* [http-model](http-model.md)
-* [uri-model](uri-model.md)
-* [marshalling](marshalling.md)
-* [unmarshalling](unmarshalling.md)
-* [encoding](encoding.md)
-* [json-support](json-support.md)
-* [timeouts](timeouts.md)
-
-@@@
diff --git a/docs/src/main/paradox/java/http/common/index.md b/docs/src/main/paradox/java/http/common/index.md
new file mode 120000
index 00000000000..b9bdb5092a3
--- /dev/null
+++ b/docs/src/main/paradox/java/http/common/index.md
@@ -0,0 +1 @@
+../../../scala/http/common/index.md
\ No newline at end of file
diff --git a/docs/src/main/paradox/java/http/common/marshalling.md b/docs/src/main/paradox/java/http/common/marshalling.md
index 85003f7f15c..51c40c250ee 100644
--- a/docs/src/main/paradox/java/http/common/marshalling.md
+++ b/docs/src/main/paradox/java/http/common/marshalling.md
@@ -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.
+
## Custom Marshallers
Akka HTTP gives you a few convenience tools for constructing marshallers for your own types.
diff --git a/docs/src/main/paradox/java/http/common/xml-support.md b/docs/src/main/paradox/java/http/common/xml-support.md
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/docs/src/main/paradox/java/http/common/xml-support.md b/docs/src/main/paradox/java/http/common/xml-support.md
new file mode 120000
index 00000000000..fb7d656110d
--- /dev/null
+++ b/docs/src/main/paradox/java/http/common/xml-support.md
@@ -0,0 +1 @@
+../../../scala/http/common/xml-support.md
\ No newline at end of file
diff --git a/docs/src/main/paradox/scala/http/common/xml-support.md b/docs/src/main/paradox/scala/http/common/xml-support.md
index 7bd1d52606d..cb72671643a 100644
--- a/docs/src/main/paradox/scala/http/common/xml-support.md
+++ b/docs/src/main/paradox/scala/http/common/xml-support.md
@@ -4,7 +4,46 @@ 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
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ $jackson.version$
+
+ ```
+ @@@
+
+@@@
+
+@@@ 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
@@ -12,15 +51,39 @@ For XML Akka HTTP currently provides support for [Scala XML](https://github.com/
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
+ ```
+
+ com.typesafe.akka
+ akka-http-xml_$scala.binary_version$
+ $project.version$
+
+ ```
+ @@@
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
diff --git a/docs/src/test/java/docs/http/javadsl/JacksonXmlExampleTest.java b/docs/src/test/java/docs/http/javadsl/JacksonXmlExampleTest.java
new file mode 100644
index 00000000000..5f7fd93e17d
--- /dev/null
+++ b/docs/src/test/java/docs/http/javadsl/JacksonXmlExampleTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009-2017 Lightbend Inc.
+ */
+
+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 = "34";
+ final Point point = new Point() {
+ {
+ setX(3);
+ setY(4);
+ }
+ };
+
+ @Test
+ public void marshalXml() throws Exception {
+ final Route route = route(
+ completeOK(point, JacksonXmlSupport.marshaller())
+ );
+
+ testRoute(route)
+ .run(HttpRequest.GET("/"))
+ .assertStatusCode(StatusCodes.OK)
+ .assertEntity(xml);
+ }
+
+ @Test
+ public void unmarshalXml() throws Exception {
+ final Unmarshaller 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 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 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;
+ }
+ }
+}
diff --git a/docs/src/test/java/docs/http/javadsl/JacksonXmlSupport.java b/docs/src/test/java/docs/http/javadsl/JacksonXmlSupport.java
new file mode 100644
index 00000000000..91ea9a97f60
--- /dev/null
+++ b/docs/src/test/java/docs/http/javadsl/JacksonXmlSupport.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009-2017 Lightbend Inc.
+ */
+
+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 XML_MEDIA_TYPES = Arrays.asList(MediaTypes.APPLICATION_XML, MediaTypes.TEXT_XML);
+
+ public static Marshaller marshaller() {
+ return Marshaller.wrapEntity(
+ u -> toXML(DEFAULT_XML_MAPPER, u),
+ Marshaller.stringToEntity(),
+ MediaTypes.APPLICATION_XML
+ );
+ }
+
+ public static Unmarshaller unmarshaller(Class expectedType) {
+ return Unmarshaller.forMediaTypes(XML_MEDIA_TYPES, Unmarshaller.entityToString())
+ .thenApply(xml -> fromXML(DEFAULT_XML_MAPPER, xml, expectedType));
+ }
+
+ private static 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 fromXML(ObjectMapper mapper, String xml, Class expectedType) {
+ try {
+ return mapper.readerFor(expectedType).readValue(xml);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot unmarshal XML as " + expectedType.getSimpleName(), e);
+ }
+ }
+}
+//#jackson-xml-support
diff --git a/docs/src/test/java/docs/http/javadsl/server/directives/MarshallingDirectivesExamplesTest.java b/docs/src/test/java/docs/http/javadsl/server/directives/MarshallingDirectivesExamplesTest.java
index bfe84a23b9d..64b0999db57 100644
--- a/docs/src/test/java/docs/http/javadsl/server/directives/MarshallingDirectivesExamplesTest.java
+++ b/docs/src/test/java/docs/http/javadsl/server/directives/MarshallingDirectivesExamplesTest.java
@@ -47,9 +47,9 @@ public int getFavoriteNumber() {
@Test
public void testEntity() {
//#example-entity-with-json
- final Unmarshaller unmarshallar = Jackson.unmarshaller(Person.class);
+ final Unmarshaller unmarshaller = Jackson.unmarshaller(Person.class);
- final Route route = entity(unmarshallar, person ->
+ final Route route = entity(unmarshaller, person ->
complete( "Person:" + person.getName() + " - favoriteNumber:" + person.getFavoriteNumber() )
);
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index ac372d2177c..4b54b4b72bc 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -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"
@@ -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
@@ -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 {
@@ -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)
}