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
3 changes: 3 additions & 0 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {

implementation(platform(jacksonLibs.jackson.bom))
implementation(jacksonLibs.jackson.jsr310)
implementation(jacksonLibs.jackson.parameter.names)

implementation(kotlinLibs.kotlinx.coroutines)
implementation(kotlinLibs.kotlinx.serialization.core)
Expand All @@ -40,3 +41,5 @@ application {
tasks.withType<Jar> { this.enabled = false }

tasks.withType<ShadowJar> { transform(ServiceFileTransformer::class.java) }

tasks.withType<JavaCompile> { options.compilerArgs.add("-parameters") }
16 changes: 8 additions & 8 deletions examples/src/main/java/my/restate/sdk/examples/Counter.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ public void reset(ObjectContext ctx) {
}

@Handler
public void add(ObjectContext ctx, Long request) {
public void add(ObjectContext ctx, long request) {
long currentValue = ctx.get(TOTAL).orElse(0L);
long newValue = currentValue + request;
ctx.set(TOTAL, newValue);
}

@Shared
@Handler
public Long get(SharedObjectContext ctx) {
public long get(SharedObjectContext ctx) {
return ctx.get(TOTAL).orElse(0L);
}

@Handler
public CounterUpdateResult getAndAdd(ObjectContext ctx, Long request) {
public CounterUpdateResult getAndAdd(ObjectContext ctx, long request) {
LOG.info("Invoked get and add with {}", request);

long currentValue = ctx.get(TOTAL).orElse(0L);
Expand All @@ -60,19 +60,19 @@ public static void main(String[] args) {
}

public static class CounterUpdateResult {
private final Long newValue;
private final Long oldValue;
private final long newValue;
private final long oldValue;

public CounterUpdateResult(Long newValue, Long oldValue) {
public CounterUpdateResult(long newValue, long oldValue) {
this.newValue = newValue;
this.oldValue = oldValue;
}

public Long getNewValue() {
public long getNewValue() {
return newValue;
}

public Long getOldValue() {
public long getOldValue() {
return oldValue;
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/resources/log4j2.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ appender.console.filter.replay.0.type = KeyValuePair
appender.console.filter.replay.0.key = restateInvocationStatus
appender.console.filter.replay.0.value = REPLAYING

# Restate logs to debug level
# Restate logs to info level
logger.app.name = dev.restate
logger.app.level = error
logger.app.level = info
logger.app.additivity = false
logger.app.appenderRef.console.ref = consoleLogger

Expand Down
19 changes: 18 additions & 1 deletion sdk-api/src/main/java/dev/restate/sdk/JsonSerdes.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import dev.restate.sdk.common.RichSerde;
import dev.restate.sdk.common.Serde;
import dev.restate.sdk.common.function.ThrowingBiConsumer;
import dev.restate.sdk.common.function.ThrowingFunction;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import org.jspecify.annotations.NonNull;

/**
Expand All @@ -32,6 +34,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link String}. This writes and reads {@link String} as JSON value. */
public static Serde<@NonNull String> STRING =
usingJackson(
"string",
JsonGenerator::writeString,
p -> {
if (p.nextToken() != JsonToken.VALUE_STRING) {
Expand All @@ -44,6 +47,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Boolean}. This writes and reads {@link Boolean} as JSON value. */
public static Serde<@NonNull Boolean> BOOLEAN =
usingJackson(
"boolean",
JsonGenerator::writeBoolean,
p -> {
p.nextToken();
Expand All @@ -53,6 +57,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Byte}. This writes and reads {@link Byte} as JSON value. */
public static Serde<@NonNull Byte> BYTE =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -62,6 +67,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Short}. This writes and reads {@link Short} as JSON value. */
public static Serde<@NonNull Short> SHORT =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -71,6 +77,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Integer}. This writes and reads {@link Integer} as JSON value. */
public static Serde<@NonNull Integer> INT =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -80,6 +87,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Long}. This writes and reads {@link Long} as JSON value. */
public static Serde<@NonNull Long> LONG =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -89,6 +97,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Float}. This writes and reads {@link Float} as JSON value. */
public static Serde<@NonNull Float> FLOAT =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -98,6 +107,7 @@ private JsonSerdes() {}
/** {@link Serde} for {@link Double}. This writes and reads {@link Double} as JSON value. */
public static Serde<@NonNull Double> DOUBLE =
usingJackson(
"number",
JsonGenerator::writeNumber,
p -> {
p.nextToken();
Expand All @@ -109,9 +119,16 @@ private JsonSerdes() {}
private static final JsonFactory JSON_FACTORY = new JsonFactory();

private static <T extends @NonNull Object> Serde<T> usingJackson(
String type,
ThrowingBiConsumer<JsonGenerator, T> serializer,
ThrowingFunction<JsonParser, T> deserializer) {
return new Serde<>() {
return new RichSerde<>() {

@Override
public Object jsonSchema() {
return Map.of("type", type);
}

@Override
public byte[] serialize(T value) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Expand Down
59 changes: 59 additions & 0 deletions sdk-common/src/main/java/dev/restate/sdk/common/RichSerde.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.common;

import java.nio.ByteBuffer;
import org.jspecify.annotations.Nullable;

/**
* Richer version of {@link Serde} containing schema information.
*
* <p>You can create one using {@link #withSchema(Object, Serde)}.
*/
public interface RichSerde<T extends @Nullable Object> extends Serde<T> {

/**
* @return a Draft 2020-12 Json Schema
*/
Object jsonSchema();

static <T> RichSerde<T> withSchema(Object jsonSchema, Serde<T> inner) {
return new RichSerde<T>() {
@Override
public byte[] serialize(T value) {
return inner.serialize(value);
}

@Override
public ByteBuffer serializeToByteBuffer(T value) {
return inner.serializeToByteBuffer(value);
}

@Override
public T deserialize(ByteBuffer byteBuffer) {
return inner.deserialize(byteBuffer);
}

@Override
public T deserialize(byte[] value) {
return inner.deserialize(value);
}

@Override
public String contentType() {
return inner.contentType();
}

@Override
public Object jsonSchema() {
return jsonSchema;
}
};
}
}
49 changes: 36 additions & 13 deletions sdk-core/src/main/java/dev/restate/sdk/core/EndpointManifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
import static dev.restate.sdk.core.ServiceProtocol.*;

import dev.restate.sdk.common.HandlerType;
import dev.restate.sdk.common.RichSerde;
import dev.restate.sdk.common.ServiceType;
import dev.restate.sdk.common.syscalls.HandlerDefinition;
import dev.restate.sdk.common.syscalls.HandlerSpecification;
import dev.restate.sdk.common.syscalls.ServiceDefinition;
import dev.restate.sdk.core.manifest.*;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -68,24 +70,45 @@ private static Service.Ty convertServiceType(ServiceType serviceType) {

private static Handler convertHandler(HandlerDefinition<?, ?, ?> handler) {
HandlerSpecification<?, ?> spec = handler.getSpec();
return new Handler()
.withName(spec.getName())
.withTy(convertHandlerType(spec.getHandlerType()))
.withInput(convertHandlerInput(spec))
.withOutput(convertHandlerOutput(spec));
}

private static Input convertHandlerInput(HandlerSpecification<?, ?> spec) {
String acceptContentType =
spec.getAcceptContentType() != null
? spec.getAcceptContentType()
: spec.getRequestSerde().contentType();

return new Handler()
.withName(spec.getName())
.withTy(convertHandlerType(spec.getHandlerType()))
.withInput(
acceptContentType == null
? EMPTY_INPUT
: new Input().withRequired(true).withContentType(acceptContentType))
.withOutput(
spec.getResponseSerde().contentType() == null
? EMPTY_OUTPUT
: new Output()
.withContentType(spec.getResponseSerde().contentType())
.withSetContentTypeIfEmpty(false));
Input input =
acceptContentType == null
? EMPTY_INPUT
: new Input().withRequired(true).withContentType(acceptContentType);

if (spec.getRequestSerde() instanceof RichSerde) {
input.setJsonSchema(
Objects.requireNonNull(((RichSerde<?>) spec.getRequestSerde()).jsonSchema()));
}
return input;
}

private static Output convertHandlerOutput(HandlerSpecification<?, ?> spec) {
Output output =
spec.getResponseSerde().contentType() == null
? EMPTY_OUTPUT
: new Output()
.withContentType(spec.getResponseSerde().contentType())
.withSetContentTypeIfEmpty(false);

if (spec.getResponseSerde() instanceof RichSerde) {
output.setJsonSchema(
Objects.requireNonNull(((RichSerde<?>) spec.getResponseSerde()).jsonSchema()));
}

return output;
}

private static Handler.Ty convertHandlerType(HandlerType handlerType) {
Expand Down
3 changes: 3 additions & 0 deletions sdk-serde-jackson/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
api(jacksonLibs.jackson.databind)
implementation(jacksonLibs.jackson.core)

implementation("com.github.victools:jsonschema-generator:4.37.0")
implementation("com.github.victools:jsonschema-module-jackson:4.37.0")

testImplementation(testingLibs.junit.jupiter)
testImplementation(testingLibs.assertj)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.victools.jsonschema.generator.*;
import com.github.victools.jsonschema.module.jackson.JacksonModule;
import com.github.victools.jsonschema.module.jackson.JacksonOption;
import dev.restate.sdk.common.RichSerde;
import dev.restate.sdk.common.Serde;
import java.io.IOException;
import org.jspecify.annotations.Nullable;

/**
* {@link Serde} implementations for Jackson.
Expand Down Expand Up @@ -40,11 +45,21 @@ public final class JacksonSerdes {
private JacksonSerdes() {}

private static final ObjectMapper defaultMapper;
private static final SchemaGenerator schemaGenerator;

static {
defaultMapper = new ObjectMapper();
// Find modules through SPI (e.g. jackson-datatype-jsr310)
defaultMapper.findAndRegisterModules();

JacksonModule module =
new JacksonModule(
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED, JacksonOption.INLINE_TRANSFORMED_SUBTYPES);
SchemaGeneratorConfigBuilder configBuilder =
new SchemaGeneratorConfigBuilder(
defaultMapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
.with(module);
schemaGenerator = new SchemaGenerator(configBuilder.build());
}

/** Serialize/Deserialize class using the default object mapper. */
Expand All @@ -54,7 +69,12 @@ public static <T> Serde<T> of(Class<T> clazz) {

/** Serialize/Deserialize class using the provided object mapper. */
public static <T> Serde<T> of(ObjectMapper mapper, Class<T> clazz) {
return new Serde<>() {
return new RichSerde<>() {
@Override
public @Nullable Object jsonSchema() {
return schemaGenerator.generateSchema(clazz);
}

@Override
public byte[] serialize(T value) {
try {
Expand Down Expand Up @@ -89,7 +109,12 @@ public static <T> Serde<T> of(TypeReference<T> typeReference) {

/** Serialize/Deserialize {@link TypeReference} using the default object mapper. */
public static <T> Serde<T> of(ObjectMapper mapper, TypeReference<T> typeReference) {
return new Serde<>() {
return new RichSerde<>() {
@Override
public @Nullable Object jsonSchema() {
return schemaGenerator.generateSchema(typeReference.getType());
}

@Override
public byte[] serialize(T value) {
try {
Expand Down
5 changes: 5 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ dependencyResolutionManagement {
.withoutVersion()
library("jackson-jdk8", "com.fasterxml.jackson.datatype", "jackson-datatype-jdk8")
.withoutVersion()
library(
"jackson-parameter-names",
"com.fasterxml.jackson.module",
"jackson-module-parameter-names")
.withoutVersion()
}
create("kotlinLibs") {
library("kotlinx-coroutines", "org.jetbrains.kotlinx", "kotlinx-coroutines-core")
Expand Down
Loading