Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ lazy val httpMarshallersJava = project("http-marshallers-java")
.enablePlugins(NoPublish /*, AggregatePRValidation*/ )
.disablePlugins(MimaPlugin)
.aggregate(httpJackson)
.aggregate(httpJackson3)

lazy val httpJackson =
httpMarshallersJavaSubproject("jackson")
Expand All @@ -264,6 +265,16 @@ lazy val httpJackson =
.settings(Dependencies.httpJackson)
.enablePlugins(ScaladocNoVerificationOfDiagrams)

lazy val httpJackson3 =
httpMarshallersJavaSubproject("jackson3")
.settings(AutomaticModuleName.settings("pekko.http.marshallers.jackson3"))
.addPekkoModuleDependency("pekko-stream", "provided", PekkoCoreDependency.default)
.addPekkoModuleDependency("pekko-stream-testkit", "test", PekkoCoreDependency.default)
.dependsOn(httpTestkit % "test")
.settings(Dependencies.httpJackson3)
.enablePlugins(ScaladocNoVerificationOfDiagrams)
.disablePlugins(MimaPlugin)

lazy val httpCaching = project("http-caching")
.settings(
name := "pekko-http-caching")
Expand Down
21 changes: 21 additions & 0 deletions docs/src/main/paradox/common/json-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ To make use of the support module for (un)marshalling from and to JSON with [Jac
version="PekkoHttpVersion"
}

`pekko-http-jackson` supports Jackson v2 while `pekko-http-jackson3` supports Jackson v3 (see below for details).

Use `org.apache.pekko.http.javadsl.marshallers.jackson.Jackson.unmarshaller(T.class)` to create an @apidoc[Unmarshaller[HttpEntity,T]] which expects the request
body (HttpEntity) to be of type `application/json` and converts it to `T` using Jackson.

Expand All @@ -30,8 +32,27 @@ Use `org.apache.pekko.http.javadsl.marshallers.jackson.Jackson.marshaller(T.clas

@@snip [PetStoreExample.java](/http-tests/src/main/java/org/apache/pekko/http/javadsl/server/examples/petstore/PetStoreExample.java) { #imports #marshall }

`pekko-http-jackson` uses a Jackson `ObjectMapper` config that closely matches the Jackson default config.
If you want to control the setup of the `ObjectMapper` yourself, you can build your own
using the Jackson APIs. There are methods in `org.apache.pekko.http.javadsl.marshallers.jackson.Jackson`
that can take an `ObjectMapper` instance as input instead of having `pekko-http-jackson` build one for you.

Refer to @github[this file](/http-tests/src/main/java/org/apache/pekko/http/javadsl/server/examples/petstore/PetStoreExample.java) in the sources for the complete example.

We also now support Jackson v3.

@@dependency [sbt,Gradle,Maven] {
bomGroup2="org.apache.pekko" bomArtifact2="pekko-http-bom_$scala.binary.version$" bomVersionSymbols2="PekkoHttpVersion"
symbol="PekkoHttpVersion"
value="$project.version$"
group="org.apache.pekko"
artifact="pekko-http-jackson3_$scala.binary.version$"
version="PekkoHttpVersion"
}

`pekko-http-jackson3` works in much the same way as `pekko-http-jackson` but uses the newer
version of Jackson libs (`tool.jackson` jars). This lib has a class called `org.apache.pekko.http.javadsl.marshallers.jackson3.Jackson`.

@@@


Expand Down
6 changes: 5 additions & 1 deletion docs/src/main/paradox/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,9 @@ Details can be found here: @ref[XML Support](common/xml-support.md)
@@@
@@@ div { .group-java }
pekko-http-jackson
: Predefined glue-code for (de)serializing custom types from/to JSON with [jackson](https://github.com/FasterXML/jackson)
: Predefined glue-code for (de)serializing custom types from/to JSON with [jackson v2.x](https://github.com/FasterXML/jackson)
@@@
@@@ div { .group-java }
pekko-http-jackson3
: Predefined glue-code for (de)serializing custom types from/to JSON with [jackson v3.x](https://github.com/FasterXML/jackson)
@@@
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ like to stream a different content type (for example plists or protobuf).

Firstly, we'll need to get some additional marshalling infrastructure set up, that is able to marshal to and from an
Apache Pekko Streams @apidoc[Source[T, ?]]. Here we'll use the `Jackson` helper class from `pekko-http-jackson` (a separate library
that you should add as a dependency if you want to use Jackson with Apache Pekko HTTP).
that you should add as a dependency if you want to use Jackson v2.x with Apache Pekko HTTP).
There is also `pekko-http-jackson3` if you prefer Jackson v3.x.

First we enable JSON Streaming by making an implicit @apidoc[EntityStreamingSupport] instance available (Step 1).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/

/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/

package org.apache.pekko.http.javadsl.marshallers.jackson3;

import java.io.IOException;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.pekko.http.javadsl.model.HttpEntity;
import org.apache.pekko.http.javadsl.model.MediaTypes;
import org.apache.pekko.http.javadsl.model.RequestEntity;
import org.apache.pekko.http.javadsl.marshalling.Marshaller;
import org.apache.pekko.http.javadsl.unmarshalling.Unmarshaller;
import org.apache.pekko.http.scaladsl.model.ExceptionWithErrorInfo;
import org.apache.pekko.http.scaladsl.model.ErrorInfo;
import org.apache.pekko.util.ByteString;

import tools.jackson.core.JacksonException;
import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.StreamWriteConstraints;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.util.BufferRecycler;
import tools.jackson.core.util.JsonRecyclerPools;
import tools.jackson.core.util.RecyclerPool;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;

/** A JSON marshaller/unmarshaller using the Jackson library. */
public class Jackson {
private static final ObjectMapper defaultObjectMapper = createMapper();

/** INTERNAL API */
public static class JacksonUnmarshallingException extends ExceptionWithErrorInfo {
public JacksonUnmarshallingException(Class<?> expectedType, Exception cause) {
super(
new ErrorInfo(
"Cannot unmarshal JSON as " + expectedType.getSimpleName(), cause.getMessage()),
cause);
}
}

public static <T> Marshaller<T, RequestEntity> marshaller() {
return marshaller(defaultObjectMapper);
}

public static <T> Marshaller<T, RequestEntity> marshaller(ObjectMapper mapper) {
return Marshaller.wrapEntity(
u -> toJSON(mapper, u), Marshaller.stringToEntity(), MediaTypes.APPLICATION_JSON);
}

public static <T> Unmarshaller<ByteString, T> byteStringUnmarshaller(Class<T> expectedType) {
return byteStringUnmarshaller(defaultObjectMapper, expectedType);
}

public static <T> Unmarshaller<HttpEntity, T> unmarshaller(Class<T> expectedType) {
return unmarshaller(defaultObjectMapper, expectedType);
}

public static <T> Unmarshaller<HttpEntity, T> unmarshaller(
ObjectMapper mapper, Class<T> expectedType) {
return Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, Unmarshaller.entityToString())
.thenApply(s -> fromJSON(mapper, s, expectedType));
}

public static <T> Unmarshaller<ByteString, T> byteStringUnmarshaller(
ObjectMapper mapper, Class<T> expectedType) {
return Unmarshaller.sync(s -> fromJSON(mapper, s.utf8String(), expectedType));
}

private static String toJSON(ObjectMapper mapper, Object object) {
try {
return mapper.writeValueAsString(object);
} catch (JacksonException e) {
throw new IllegalArgumentException("Cannot marshal to JSON: " + object, e);
}
}

private static <T> T fromJSON(ObjectMapper mapper, String json, Class<T> expectedType) {
try {
return mapper.readerFor(expectedType).readValue(json);
} catch (JacksonException e) {
throw new JacksonUnmarshallingException(expectedType, e);
}
}

private static ObjectMapper createMapper() {
return createMapper(ConfigFactory.load().getConfig("pekko.http.marshallers.jackson3"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to make it overrideable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API methods allow you to provide your own ObjectMapper so this code is just for the case where we need to create an ObjectMapper instance because the user hasn't provided one.
If we were to open this up, I would prefer if users were requesting it. This is a copy of the code from pekko-http-jackson and noone was requesting any changes to this for that module.

}

static JsonFactory createJsonFactory(final Config config) {
StreamReadConstraints streamReadConstraints =
StreamReadConstraints.builder()
.maxNestingDepth(config.getInt("read.max-nesting-depth"))
.maxNumberLength(config.getInt("read.max-number-length"))
.maxStringLength(config.getInt("read.max-string-length"))
.maxNameLength(config.getInt("read.max-name-length"))
.maxDocumentLength(config.getLong("read.max-document-length"))
.maxTokenCount(config.getLong("read.max-token-count"))
.build();
StreamWriteConstraints streamWriteConstraints =
StreamWriteConstraints.builder()
.maxNestingDepth(config.getInt("write.max-nesting-depth"))
.build();
return JsonFactory.builder()
.streamReadConstraints(streamReadConstraints)
.streamWriteConstraints(streamWriteConstraints)
.recyclerPool(getBufferRecyclerPool(config))
.build();
}

static ObjectMapper createMapper(final Config config) {
return JsonMapper.builder(createJsonFactory(config))
// Jackson 3 changed FAIL_ON_UNKNOWN_PROPERTIES default to false
// we keep it false because it is more secure to fail on unknown properties
// and it is consistent with previous pekko-http-jackson behavior
// and there are tests depending on this behavior
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
}

private static RecyclerPool<BufferRecycler> getBufferRecyclerPool(final Config cfg) {
final String poolType = cfg.getString("buffer-recycler.pool-instance");
switch (poolType) {
case "thread-local":
return JsonRecyclerPools.threadLocalPool();
case "concurrent-deque":
return JsonRecyclerPools.newConcurrentDequePool();
case "shared-concurrent-deque":
return JsonRecyclerPools.sharedConcurrentDequePool();
case "bounded":
return JsonRecyclerPools.newBoundedPool(cfg.getInt("buffer-recycler.bounded-pool-size"));
case "non-recycling":
return JsonRecyclerPools.nonRecyclingPool();
default:
throw new IllegalArgumentException("Unknown recycler-pool: " + poolType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SPDX-License-Identifier: Apache-2.0

###################################
# Pekko HTTP Jackson3 Config File #
###################################

# This is the reference config file that contains all the default settings.
# Make your edits/overrides in your application.conf.

pekko.http.marshallers.jackson3 {
read {
# see https://www.javadoc.io/doc/tools.jackson.core/jackson-core/3.0.0/tools.jackson.core/tools/jackson/core/StreamReadConstraints.html
# these defaults are the same as the defaults in `StreamReadConstraints`
max-nesting-depth = 1000
max-number-length = 1000
max-string-length = 20000000
max-name-length = 50000
# max-document-length of -1 means unlimited
max-document-length = -1
# max-token-count of -1 means unlimited
max-token-count = -1
}

write {
# see https://www.javadoc.io/doc/tools.jackson.core/jackson-core/3.0.0/tools.jackson.core/tools/jackson/core/StreamWriteConstraints.html
# these defaults are the same as the defaults in `StreamWriteConstraints`
max-nesting-depth = 1000
}

# Controls the Buffer Recycler Pool implementation used by Jackson.
# https://www.javadoc.io/doc/tools.jackson.core/jackson-core/3.0.0/tools.jackson.core/tools/jackson/core/util/JsonRecyclerPools.html
# The default is "thread-local" which is the same as the default in Jackson 2.18.
buffer-recycler {
# the supported values are "thread-local", "concurrent-deque", "shared-concurrent-deque", "bounded", "non-recycling"
pool-instance = "thread-local"
# the maximum size of bounded recycler pools - must be >=1 or an IllegalArgumentException will occur
# only applies to pool-instance type "bounded"
bounded-pool-size = 100
}
}
Loading